/*
 * The `hidden` attribute loses to any rule that sets `display`, and several
 * things here are display: flex or grid — which is how the countdown's hide
 * button quietly stopped working. This puts the attribute back in charge for
 * everything, so hiding an element is never a question of specificity.
 */
[hidden] { display: none !important; }

/*
 * The whole app is dark, and the browser needs telling.
 *
 * Without this, everything the browser draws itself stays light: the little
 * calendar glyph inside a date field, the picker that drops out of it, the
 * scrollbars. A date input styled dark by hand but left light here is the
 * worst of both — a black box with a black icon in it.
 */
/*
 * The root size, and the six steps everything is meant to be one of.
 *
 * There was no `font-size` here at all, so every rem in the file was a
 * fraction of the browser's 16px — and nearly all of them were a fraction
 * below one. Two hundred of the two hundred and seventy `font-size`
 * declarations in this file were under 1rem, across thirty-six different
 * values, and the smallest thing on the home page was eight pixels. Maria
 * wears glasses and said so.
 *
 * A percentage rather than `17px`: a person who has turned their browser's
 * own default up has done that on purpose, and a pixel here would quietly
 * take it back off them. 106.25% of the usual 16 is 17, and of their 20 is 21.
 *
 * The floor is the part that matters most. `--fs-label` is the smallest text
 * the app is allowed to draw, and it is thirteen pixels; the badges and the
 * little grey facts that used to sit at eight, nine and eleven are all at it
 * now. Nothing below it should ever be written again — if something does not
 * fit at thirteen pixels, the thing to change is the layout.
 */
:root {
    color-scheme: dark;
    font-size: 106.25%;

    --fs-label: 0.78rem;   /* 13px — the floor, for badges and small facts */
    --fs-small: 0.86rem;   /* 15px — supporting lines */
    --fs-body:  1rem;      /* 17px — anything you actually read */
    --fs-lead:  1.2rem;    /* 20px — the note in the jar */
    --fs-head:  1.5rem;    /* 26px — a page's own heading */
    --fs-hero:  3.4rem;    /* 58px — the number, and only the number */

    /* One column down the jar. The card, the buttons, the shortcuts and the
       folded list are stacked blocks of one thing, and on a wide screen four
       different widths read as four unrelated panels — which is what they
       were: 780, 780, 557 and 520. */
    --jar-width: 520px;

    /* The page as a whole. It was 780px on every screen, which on a desktop
       left two thirds of it empty while the board scrolled sideways and the
       calendar cut its entries off at a dozen letters. The pages that are
       read rather than laid out keep a narrower measure of their own —
       --read-width — so a line of text does not run the width of a monitor. */
    --app-width: 1240px;
    --read-width: 780px;
}

/*
 * Every text field and dropdown, in one place.
 *
 * This should have existed from the start. There was no shared rule, so each
 * form carried its own copy of the same five declarations — and a form that
 * forgot got the browser's default, which on this background is a white slab.
 * That is exactly how the calendar shipped.
 *
 * `:where()` keeps the specificity at zero, so every existing per-form rule
 * still wins and nothing already on screen moves. Checkboxes, radios and file
 * inputs are left alone: they are not boxes you type in, and giving them a
 * padded frame would make a tick look like a text field.
 */
input:where(:not([type="checkbox"], [type="radio"], [type="file"])),
select {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 9px 12px;
    font-family: inherit;
    font-size: 0.95rem;
    outline: none;
    /* Or a `width: 100%` box is 100% *plus* its padding and border. */
    box-sizing: border-box;
}

input:where(:not([type="checkbox"], [type="radio"], [type="file"])):focus,
select:focus { border-color: #8a2be2; }

/*
 * The list a dropdown drops open is drawn by the operating system, and on
 * Windows that is a pale grey however dark the control above it is. Two places
 * had already discovered this and carried their own copy of this rule; here it
 * is once, so the next dropdown added does not have to find out the same way.
 */
select option { background: #1a1030; color: #ffffff; }

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    height: 100dvh;
    margin: 0;
    color: #ffffff;
    background-color: #080312;
    overflow: hidden;
    position: relative;
}

/*
 * Fixed, not absolute.
 *
 * It was one viewport tall and pinned to the top of the document, which is the
 * same thing on a page that does not scroll — and every page scrolls now. Past
 * the first screenful the aurora simply ended and the rest of the page sat on
 * flat #080312: a calendar form, the lower half of a board, the admin log, all
 * on near-black while the top of the same page glowed. Fixed keeps it behind
 * the viewport wherever you are, which is what it always looked like it did.
 */
.aurora-bg {
    position: fixed;
    inset: 0;
    overflow: hidden;
    z-index: -1;
    filter: blur(90px);
}

.orb { position: absolute; border-radius: 50%; opacity: 0.6; }
.orb-1 { width: 70vw; height: 70vw; background-color: #ffb6c1; top: -20%; left: -10%; animation: float1 25s infinite linear; }
.orb-2 { width: 80vw; height: 80vw; background-color: #8a2be2; bottom: -30%; right: -20%; animation: float2 30s infinite linear; }
.orb-3 { width: 50vw; height: 50vw; background-color: #ff1493; top: 20%; left: 20%; animation: float3 20s infinite linear; }
.orb-4 { width: 90vw; height: 90vw; background-color: #191970; top: -10%; right: 10%; animation: float4 35s infinite linear; }

@keyframes float1 { 0% { transform: translate(0, 0); } 33% { transform: translate(30vw, 15vh); } 66% { transform: translate(10vw, 40vh); } 100% { transform: translate(0, 0); } }
@keyframes float2 { 0% { transform: translate(0, 0); } 33% { transform: translate(-30vw, -20vh); } 66% { transform: translate(-50vw, 10vh); } 100% { transform: translate(0, 0); } }
@keyframes float3 { 0% { transform: translate(0, 0); } 50% { transform: translate(40vw, -10vh); } 100% { transform: translate(0, 0); } }
@keyframes float4 { 0% { transform: translate(0, 0); } 50% { transform: translate(-20vw, 30vh); } 100% { transform: translate(0, 0); } }

.countdown-container {
    text-align: center;
    background: rgba(255, 255, 255, 0.05);
    width: 90%;
    max-width: 600px;
    box-sizing: border-box;
    padding: 50px 30px 40px 30px;
    border-radius: 15px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
    border: 1px solid rgba(255, 255, 255, 0.1);
    position: relative;
    z-index: 10;
    backdrop-filter: blur(10px);
}

.lang-select {
    position: absolute;
    top: 15px;
    right: 15px;
    background: rgba(255, 255, 255, 0.1);
    color: #a8b2d1;
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 10px;
    /* 44 like everything else. These two are the only controls left in the
       header now the bar has gone to the bottom, and they were 31 and 34
       pixels tall — the two smallest targets on the page. */
    min-height: 44px;
    padding: 5px 10px;
    font-size: var(--fs-small);
    cursor: pointer;
    outline: none;
    z-index: 20;
}

.lang-select:focus { border-color: #ffb6c1; }
.lang-select option { background: #1e1e2f; color: #ffffff; }

.toggle-timer-btn {
    position: absolute;
    top: 15px;
    left: 15px;
    background: rgba(255, 255, 255, 0.1);
    color: #ffffff;
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 10px;
    min-height: 44px;
    min-width: 44px;
    padding: 5px 8px;
    font-size: var(--fs-body);
    cursor: pointer;
    outline: none;
    z-index: 20;
    transition: all 0.2s ease;
}
.toggle-timer-btn:hover {
    border-color: #ffb6c1;
    background: rgba(255, 255, 255, 0.2);
}

h1 {
    font-size: 1.5rem;
    margin-bottom: 30px;
    margin-top: 10px;
    color: #a8b2d1;
    font-weight: normal;
}

#timer {
    display: flex;
    justify-content: center;
    gap: 20px;
    color: #ffb6c1;
    text-shadow: 0 0 10px rgba(255, 182, 193, 0.6), 0 0 20px rgba(255, 182, 193, 0.3);
    font-variant-numeric: tabular-nums;
}

.time-block { display: flex; align-items: baseline; gap: 5px; }

/* ---------------------------------------------------------------
   The countdown, with a hierarchy.

   Four numbers the same size is four things to read. The days are
   the answer to the question anybody is asking; the rest is how
   precise the answer is, and belongs under it in smaller type.

   All of it by id, so it beats the plain `.num` in the phone-width
   block further down without that rule having to know about this.
   --------------------------------------------------------------- */
#timer:not(.compact) { flex-wrap: wrap; row-gap: 2px; }
#timer:not(.compact) .time-block:first-child {
    flex-basis: 100%;
    justify-content: center;
}
#timer:not(.compact) .time-block:first-child .num { font-size: var(--fs-hero); }
#timer:not(.compact) .time-block:first-child .unit { font-size: var(--fs-head); }
#timer:not(.compact) .time-block:not(:first-child) .num {
    font-size: 1.4rem;
    font-weight: 600;
    letter-spacing: 1px;
    text-shadow: none;
}
#timer:not(.compact) .time-block:not(:first-child) .unit { font-size: var(--fs-label); }
.num { font-size: 4rem; font-weight: bold; letter-spacing: 2px; }
.unit { font-size: 1.2rem; color: #a8b2d1; font-weight: normal; text-shadow: none; }

/* One primary action and one beside it. `#cookieBtn` keeps its own colour;
   Add is an outline, because two filled buttons in two colours is the thing
   this is undoing. */
.jar-actions {
    display: flex;
    gap: 10px;
    margin-top: 18px;
}

.jar-actions button {
    min-height: 52px;
    border-radius: 14px;
    font-size: var(--fs-body);
}

/* Takes whatever Add does not. It had a 320px ceiling and the row was
   centred inside the full page width, which is why the buttons sat in from
   both edges while everything under them reached further out. */
.jar-actions #cookieBtn { flex: 1; min-width: 0; }

#addBtn {
    background: transparent;
    border: 1px solid rgba(138, 43, 226, 0.75);
    color: #c9a6ff;
    box-shadow: none;
}
#addBtn:hover { background: rgba(138, 43, 226, 0.18); color: #fff; }

button {
    padding: 12px 24px;
    font-size: 1rem;
    color: #1e1e2f;
    background-color: #ffb6c1;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    font-weight: bold;
    transition: all 0.2s ease;
    box-shadow: 0 0 10px rgba(255, 182, 193, 0.3);
    touch-action: manipulation;
    position: relative; /* Crucial for positioning the NEW badge */
}

button:hover { background-color: #ffc0cb; transform: scale(1.05); }
button:disabled { opacity: 0.5; cursor: default; transform: none; }

/* The Heart Button Styling */
#cookieBtn {
    background-color: #ff6b81;
    color: white;
    box-shadow: 0 0 10px rgba(255, 107, 129, 0.4);
}
#cookieBtn:hover { background-color: #ff4757; }

/* The Pulsing NEW Badge */
.new-badge {
    display: none; /* Hidden by default */
    position: absolute;
    top: -10px;
    right: -10px;
    background-color: #ffeb3b;
    color: #1e1e2f;
    font-size: var(--fs-label);
    padding: 4px 8px;
    border-radius: 12px;
    font-weight: 900;
    letter-spacing: 1px;
    box-shadow: 0 0 10px rgba(255, 235, 59, 0.8);
    animation: pulseBadge 1.5s infinite;
    z-index: 50;
}

/* The Rotating Spinner */
.spinner {
    display: inline-block;
    animation: spinSync 1s linear infinite;
    margin-left: 6px;
    font-weight: bold;
}

@keyframes spinSync {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

@keyframes pulseBadge {
    0% { transform: scale(1); }
    50% { transform: scale(1.15); }
    100% { transform: scale(1); }
}

.input-section {
    display: none;
    margin-top: 14px;
    flex-direction: column;
    gap: 10px;
    align-items: stretch;
}

/* The shared `textarea` rule is 90% of whatever holds it, which inside a
   column of its own is a box that stops short of both edges. */
.input-section textarea {
    width: 100%;
    box-sizing: border-box;
}
.input-section #submitBtn { align-self: center; min-height: 44px; }

textarea {
    width: 90%;
    height: 60px;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.3);
    color: white;
    border-radius: 8px;
    padding: 10px;
    font-family: inherit;
    resize: none;
    outline: none;
}

textarea:focus { border-color: #8a2be2; }

#submitBtn { margin-top: 0; padding: 8px 20px; background-color: #64ffda; color: #1e1e2f; }

/* ---------------------------------------------------------------
   The jar is one column.

   `box-sizing` is said here because the sheet does not say it
   globally — only for inputs, which found this out first. Without
   it the card's `max-width: 520px` meant 520 *plus* its padding and
   border, so the one block with padding was the one block that did
   not line up with the rest.
   --------------------------------------------------------------- */

.note-card,
.jar-actions,
.input-section,
.jar-shortcuts,
.coming-up {
    box-sizing: border-box;
    width: 100%;
    max-width: var(--jar-width);
    margin-inline: auto;
}

/* ---------------------------------------------------------------
   The note, in a card of its own and at the top of the page.
   --------------------------------------------------------------- */

.note-card {
    padding: 20px 18px;
    border-radius: 18px;
    background: rgba(255, 255, 255, 0.055);
    border: 1px solid rgba(255, 255, 255, 0.11);
}

#message {
    margin: 0;
    font-size: var(--fs-lead);
    line-height: 1.45;
    color: #ffffff;
    /* Roman, not italic. This is the one thing on the page somebody came to
       read, and a whole paragraph of italic is the hardest way to set it for
       anybody whose eyes are already working at it. The occasion line above
       carries the note's own voice instead. */
    font-style: normal;
    padding: 0;
    unicode-bidi: plaintext;
}

/* Reactions at one end, the two small buttons at the other. They were two
   stacked rows, centred, which made the card twice as tall as it needed. */
.note-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid rgba(255, 255, 255, 0.07);
}

.particle {
    position: fixed;
    border-radius: 3px;
    pointer-events: none;
    z-index: 9999;
    top: 0;
    left: 0;
    opacity: 1;
}

/* ---------------------------------------------------------------
   The passphrase gate. The jar is private; the countdown is not,
   so only the jar controls hide when signed out.
   --------------------------------------------------------------- */

.signed-out .needs-auth { display: none !important; }

/* Lists, album and admin are private in their entirety — every one of them is
   a `.view.page`. Marking each control inside them `needs-auth` would work
   until the day somebody adds one and forgets. Only the jar's public countdown
   survives being signed out, and that lives in the header. */
.signed-out .view.page { display: none !important; }
.signed-in #authGate { display: none; }

#authGate {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    margin-top: 32px;
}

#authPrompt {
    color: #a8b2d1;
    font-size: 0.95rem;
}

#authPassphrase {
    width: 70%;
    max-width: 280px;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.3);
    color: white;
    border-radius: 8px;
    padding: 10px 12px;
    font-family: inherit;
    font-size: 1rem;
    text-align: center;
    outline: none;
}

#authPassphrase:focus { border-color: #ff6b81; }

/* Matches #cookieBtn — the two are the jar's way in, and read as one family. */
#authSubmit {
    background-color: #ff6b81;
    color: white;
    padding: 10px 22px;
    box-shadow: 0 0 10px rgba(255, 107, 129, 0.4);
}
#authSubmit:hover { background-color: #ff4757; }

#authError {
    color: #ff8fa3;
    font-size: 0.9rem;
    min-height: 1.2em;
}

/* Shown while /api/session is still in flight. A passphrase box sitting there
   during the check reads as "you were signed out" — the opposite of what is
   usually happening, since most loads end up signed in. */
#authChecking {
    display: none;
    align-items: center;
    gap: 10px;
    color: #a8b2d1;
    font-size: 0.95rem;
    /* Roughly the height of the form it replaces, so the panel doesn't jump. */
    min-height: 44px;
}

.checking #authChecking { display: flex; }
.checking #authPrompt,
.checking #authPassphrase,
.checking #authSubmit,
.checking #authError { display: none; }

.auth-spinner {
    width: 18px;
    height: 18px;
    border: 2px solid rgba(255, 182, 193, 0.25);
    border-top-color: #ff6b81;
    border-radius: 50%;
    animation: spinSync 0.8s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
    /* Still turns — a frozen ring says "stuck" — just slowly enough not to nag. */
    .auth-spinner { animation-duration: 2.4s; }
}

/* The thin bar across the top while a view is loading its data — see
   lib/router.js. Fixed and above everything else, because the point of it is
   being visible the instant a tab is tapped, before the new view has even
   finished replacing the old one. */
.route-progress {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    z-index: 500;
    overflow: hidden;
    pointer-events: none;
}

/* Sticky rather than fixed: it pushes the header down instead of sitting over
   it, so nothing is ever hidden behind it. */
.outage-banner {
    position: sticky;
    top: 0;
    z-index: 600;
    background: #ff6b6b;
    color: #1e1e2f;
    font-weight: 600;
    text-align: center;
    padding: 10px 16px;
}

/* One bar for everything the app says, floating over the page it is about.
   Fixed rather than in the flow: what it has to say is usually about the thing
   you just pressed, which on a long page can be far from where a line of page
   text would sit. */
.notice {
    position: fixed;
    /* Under the bar at the top rather than over it: a warning waits to be
       closed, and one sitting on the navigation is one you have to dismiss
       before you can leave the page it is about. */
    top: 64px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 700;
    display: flex;
    align-items: center;
    gap: 10px;
    max-width: min(92vw, 520px);
    padding: 10px 12px 10px 16px;
    border-radius: 12px;
    background: rgba(30, 18, 52, 0.96);
    border: 1px solid rgba(255, 255, 255, 0.16);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
    color: #e6e0f5;
    font-size: 0.92rem;
    text-align: start;
}
.notice.ok { border-color: rgba(100, 255, 218, 0.5); color: #b6ffe9; }
.notice.warn { border-color: rgba(255, 184, 107, 0.55); color: #ffd2a1; }
.notice-close {
    flex: none;
    padding: 2px 6px;
    font-size: 0.85rem;
    line-height: 1;
    color: inherit;
    background: transparent;
    border: none;
    box-shadow: none;
    opacity: 0.7;
}
.notice-close:hover { opacity: 1; background: rgba(255, 255, 255, 0.12); transform: none; }
.notice-close:focus-visible { outline: 2px solid #8a2be2; outline-offset: 2px; }

@media (max-width: 600px) {
    .notice { top: 56px; padding: 9px 10px 9px 14px; font-size: 0.86rem; }
}

/* Whatever a view was showing before the connection went — a countdown that
   can no longer be trusted to be current, a list that might have changed
   underneath it — is worse sitting next to an error saying so than gone. */
#viewRoot.outage-hidden { display: none; }
.route-progress::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 40%;
    background: linear-gradient(90deg, transparent, #ff6b81, #8a2be2, transparent);
    animation: routeProgressSweep 1.1s ease-in-out infinite;
}
@keyframes routeProgressSweep {
    0% { left: -40%; }
    100% { left: 100%; }
}
@media (prefers-reduced-motion: reduce) {
    .route-progress::before { animation-duration: 2.8s; }
}

/* Edit / delete affordances on the note currently on screen. */
.note-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    flex: none;
}

.note-actions button {
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.15);
    color: #a8b2d1;
    box-shadow: none;
    /* 40 rather than the 44 everything else clears: these two sit inside a
       card, beside the reactions, and are the one place on the page where a
       bigger target would cost the card a line of its own. */
    min-height: 40px;
    min-width: 40px;
    padding: 0 10px;
    font-size: var(--fs-body);
    font-weight: normal;
}

.note-actions button:hover {
    background: rgba(255, 255, 255, 0.18);
    transform: scale(1.05);
}

#noteAuthor {
    display: block;
    margin-top: 12px;
    font-size: 0.85rem;
    font-style: normal;
    color: #a8b2d1;
    opacity: 0.85;
}

#whoami {
    margin-top: 28px;
    font-size: 0.8rem;
    color: #a8b2d1;
    opacity: 0.6;
}

#signOutBtn {
    background: none;
    border: none;
    box-shadow: none;
    color: inherit;
    font: inherit;
    font-weight: normal;
    text-decoration: underline;
    /* An 18px-tall link is a hard thing to hit, and this is the one control
       on the page you would least like to hit by accident — or to miss. */
    min-height: 44px;
    padding: 0 8px;
    cursor: pointer;
}

#signOutBtn:hover { background: none; transform: none; color: #ffb6c1; }

@media (max-width: 600px) {
    .countdown-container { padding: 40px 15px 30px 15px; }
    h1 { font-size: 1.2rem; margin-top: 25px; }
    #timer { gap: 8px; }
    .num { font-size: 2.2rem; }
    .unit { font-size: 0.9rem; }
    .button-group { margin-top: 25px; }
    button { padding: 10px 20px; }
}

/* ===============================================================
   The bar, along the bottom of a phone.

   Ten tiles used to sit across the top of every page: 9.3px labels,
   two rows of them, the first thing on the screen and the furthest
   thing from the thumb holding it. Maria said the app was too busy
   before she said anything else about it, and this was most of why.

   Four tabs and More. Fixed to the bottom, five equal columns, and
   labels at the floor rather than under it. Which four is lib/nav.js's
   answer, from an order an admin arranges — see placeLinks there.

   This is the phone's shape, and everything below is written for it.
   The wide one is the same tiles moved to the top and sized to their
   words — see "the bar, along the top" further down. One set of tiles
   and one DOM in both: the thing that changes is where it is pinned
   and how many links nav.js puts in it.
   =============================================================== */

#siteNav.tabbar {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 40;

    display: grid;
    /* minmax(0, …), not a bare 1fr. A bare `1fr` is `minmax(auto, 1fr)`, and
       the auto floor is the column's own content — so a long word does not
       ellipsise, it widens its tab and takes the width from its neighbours.
       Measured at 320px in Ukrainian: Налаштування claimed 92px of the bar and
       squeezed Ще down to 24, which is half the 44 every platform asks a
       target to be. With a zero floor the columns are equal and the ellipsis
       the tab already has does the rest. */
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 0;

    background: rgba(12, 6, 26, 0.94);
    border-top: 1px solid rgba(255, 255, 255, 0.11);
    backdrop-filter: blur(12px);
    /* The home indicator on a phone without a button sits over the bottom of
       the screen; without this the words are under it. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* The tab itself, and the More button, which is one. `button` resets are
   needed on the second: the sheet gives every button a pink slab. */
#siteNav.tabbar a,
#siteNav.tabbar .nav-tools-btn {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;

    /* Comfortably past the 44px every platform asks for, and past it before
       the safe-area padding rather than with it. */
    min-height: 58px;
    padding: 6px 2px;
    box-sizing: border-box;

    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    color: #9a93b5;
    font-family: inherit;
    font-weight: normal;
    text-decoration: none;
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease;
}

#siteNav.tabbar .nav-icon {
    display: block;
    font-size: 1.2rem;
    line-height: 1;
}

#siteNav.tabbar .nav-word {
    font-size: var(--fs-label);
    line-height: 1.15;
    text-align: center;
    /* One line, and an ellipsis rather than a second row: five columns of one
       line each is the shape, and "Налаштування" wrapping would make one tab
       taller than the four beside it. The full word is on the title. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#siteNav.tabbar a:hover,
#siteNav.tabbar a:focus-visible,
#siteNav.tabbar .nav-tools-btn:hover,
#siteNav.tabbar .nav-tools-btn:focus-visible {
    color: #ffb6c1;
    background: rgba(255, 255, 255, 0.05);
    transform: none;
}

/* Where you are. A line along the top edge of the tab as well as the colour,
   so it is not colour alone that says it. */
#siteNav.tabbar a.active { color: #ffb6c1; }
#siteNav.tabbar a.active::before {
    content: "";
    position: absolute;
    top: 0;
    inset-inline: 22%;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background: #ffb6c1;
}

/* The mark on a tab in the bar: hard into the corner, clear of the glyph. */
#siteNav.tabbar .nav-mark {
    position: absolute;
    top: 4px;
    inset-inline-end: 18%;
    font-size: var(--fs-label);
    line-height: 1;
}

.admin-only { display: none; }
.is-admin .admin-only { display: inline; }

/* The rule above is (0,1,0) and the tab's own display is (0,1,1), so both
   directions are said again at a weight that wins. Cosmetic either way — the
   routes refuse her — but a link to a page you cannot open is worse than no
   link. */
#siteNav.tabbar .admin-only { display: none; }
.is-admin #siteNav.tabbar .admin-only { display: flex; }

/* --------------------------------------------------------------- More */

.nav-tools { position: relative; display: flex; }
.nav-tools-btn { width: 100%; }

/* And gone when there is nothing behind it, which a wide bar produces — every
   page fits, so nothing is left for More to hold. `hidden` alone cannot do it:
   the browser's own [hidden] rule is weaker than the `display: flex` above, so
   nav.js would set the attribute, the button would stay on screen, and tapping
   it would open an empty box. The same trap as .admin-only, forty lines up. */
.nav-tools[hidden] { display: none; }

/* Opens upwards, from the bar it belongs to. Right-aligned to the screen
   rather than to the button: the button is a fifth of a phone wide and the
   menu is not. */
.nav-tools-menu {
    position: absolute;
    bottom: calc(100% + 8px);
    inset-inline-end: 6px;
    z-index: 41;
    min-width: 190px;
    max-height: 60vh;
    overflow-y: auto;
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 14px;
    background: #171226;
    box-shadow: 0 -12px 34px rgba(0, 0, 0, 0.6);
    text-align: start;
}

/* These links sit outside the bar's grid, so none of its rules reach them. */
.nav-tools-menu a {
    display: flex;
    align-items: center;
    gap: 10px;
    /* The same 44 the tabs clear, in a list where the rows are what you aim at. */
    min-height: 44px;
    padding: 0 10px;
    border-radius: 8px;
    color: #d8d2e8;
    font-size: var(--fs-small);
    text-decoration: none;
    white-space: nowrap;
}
.nav-tools-menu .nav-icon { display: block; font-size: 1.05rem; line-height: 1; }
.nav-tools-menu a:hover,
.nav-tools-menu a:focus-visible { background: rgba(255, 255, 255, 0.08); color: #fff; }
.nav-tools-menu a.active { background: rgba(255, 182, 193, 0.18); color: #ffb6c1; }

/* Pushed to the far end of the row rather than sitting against the word. */
.nav-tools-menu .nav-mark { margin-inline-start: auto; font-size: var(--fs-label); }

/* Same trap as the tabs, said explicitly in both directions. */
.nav-tools-menu .admin-only { display: none; }
.is-admin .nav-tools-menu .admin-only { display: flex; }

/* ===============================================================
   The bar, along the top of everything wider than a phone.

   The bottom edge is where a bar belongs on a phone because that is
   where the thumb already is. On a laptop there is no thumb: there is
   a cursor that is wherever you left it, a window whose bottom edge
   is the furthest point from what you are reading, and four times the
   room across. Pinning five tabs to the bottom of a 1440px screen is
   a phone layout wearing a desktop's clothes.

   So: the same tiles, the same DOM, the same glyph over the same word
   — moved to the top, sized to their words instead of to a fifth of
   the screen each, and centred. How many of them there are is nav.js's
   answer and not this file's: six from 760px, nine from 1000 and every
   page the app has from 1240, with More dropped when nothing is left
   behind it. Only 760 is in both files, because it is the only one of
   the three that changes the bar's shape — tests/consistency.test.mjs
   holds the two copies of it to each other.
   =============================================================== */

@media (min-width: 760px) {
    #siteNav.tabbar {
        top: 0;
        bottom: auto;

        /* Not a grid: five equal columns is the phone's answer to "a tab is a
           thumb wide". Here a tab is as wide as its word, and the row sits in
           the middle of the screen rather than stretched across it. */
        display: flex;
        justify-content: center;
        flex-wrap: nowrap;

        border-top: none;
        border-bottom: 1px solid rgba(255, 255, 255, 0.11);
        /* No home indicator at the top of anything. */
        padding-bottom: 0;
    }

    #siteNav.tabbar a,
    #siteNav.tabbar .nav-tools-btn {
        flex: 0 1 auto;
        /* Wide enough that "Kit" is still a target and not a word with a box
           drawn round it; capped so "Налаштування" cannot take a fifth of the
           bar for itself. */
        min-width: 70px;
        max-width: 136px;
        /* Shorter than the phone's 58: 44 is the reach-with-a-mouse floor and
           there is no thumb to clear here. Every page's padding-top is this
           number plus a gap — see .app below. */
        min-height: 50px;
        padding: 5px 12px;
    }

    /* A touch smaller than the phone's, which had a whole column to itself. */
    #siteNav.tabbar .nav-icon { font-size: 1.05rem; }

    /* Where you are, marked on the edge nearest the page it belongs to —
       which at the top of the screen is the bottom of the tab. */
    #siteNav.tabbar a.active::before {
        top: auto;
        bottom: 0;
        border-radius: 2px 2px 0 0;
    }

    /* Opens downward now, and off the button rather than off the right edge of
       the screen: More sits somewhere near the middle of a centred row here,
       and a menu pinned to the far corner would have nothing to do with it. */
    .nav-tools-menu {
        top: calc(100% + 6px);
        bottom: auto;
        inset-inline-end: 0;
        max-height: 70vh;
        box-shadow: 0 12px 34px rgba(0, 0, 0, 0.6);
    }
}

/* ---------------------------------------------------------------
   The admin page. A document to be scanned, not a card to be read,
   so it scrolls and sits wider than the countdown.
   --------------------------------------------------------------- */

body.admin-page,
body.lists-page {
    display: block;
    height: auto;
    min-height: 100dvh;
    overflow-y: auto;
}

.page-container {
    position: relative;
    z-index: 10;
    width: min(92%, 760px);
    margin: 0 auto;
    padding: 40px 0 80px;
}

.page-header { text-align: center; }
.page-header h1 { font-size: 1.6rem; color: #a8b2d1; font-weight: normal; margin: 0 0 4px; }

.panel {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    padding: 24px;
    margin-top: 28px;
    text-align: center;
    backdrop-filter: blur(10px);
}
.panel a { color: #ffb6c1; }

/* Who has been in lately, above the log. Two lines, so it stays a header to
   the list rather than competing with it. */
.visits {
    list-style: none;
    margin: 0 0 14px;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 8px 22px;
}

.visit { display: flex; align-items: baseline; gap: 8px; font-size: 0.88rem; }
.visit-who { color: #fff; font-weight: 600; }
.visit-when { color: #a8b2d1; }
.visit-when.never { color: #6f6790; font-style: italic; }
.visit small { color: #6f6790; font-size: 0.78rem; }

.filters {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: flex-end;
    justify-content: center;
    margin: 28px 0 8px;
}

/* ------------------------------------------- testing, told apart from using
 *
 * Activity inside a quiet period is filed rather than deleted: hidden by
 * default, and clearly marked when the switch brings it back. See
 * netlify/functions/_lib/quiet.mts for what opens one. */
.testing-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.8rem;
    color: #8479a0;
    cursor: pointer;
}
.testing-toggle input { accent-color: #8a2be2; cursor: pointer; }

.quiet-notice {
    margin: 8px auto 0;
    max-width: 40em;
    padding: 8px 12px;
    border-radius: 8px;
    background: rgba(255, 184, 107, 0.12);
    border: 1px solid rgba(255, 184, 107, 0.4);
    color: #ffd7ab;
    font-size: 0.82rem;
    text-align: center;
}

.event.while-testing { opacity: 0.7; }
.event-testing {
    margin-inline-start: 8px;
    padding: 1px 7px;
    border-radius: 999px;
    background: rgba(255, 184, 107, 0.18);
    color: #ffb86b;
    font-size: var(--fs-label);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.filters label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: var(--fs-label);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #8479a0;
}

.filters select {
    background: rgba(0, 0, 0, 0.35);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 6px;
    padding: 7px 10px;
    font-family: inherit;
    font-size: 0.9rem;
    outline: none;
}
.filters select:focus { border-color: #8a2be2; }

button.ghost, a.ghost {
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: #d8d2e8;
    box-shadow: none;
    font-weight: normal;
    padding: 8px 16px;
    font-size: 0.9rem;
}
button.ghost:hover, a.ghost:hover { background: rgba(255, 255, 255, 0.16); color: #fff; }

/* ------------------------------------------------------- the video call
 *
 * On the home page under the jar's own buttons. An anchor rather than a button
 * because it goes somewhere, and `a.ghost` rules do not cover it — this one has
 * a look of its own, since it is the only thing on the page that leaves it. */
/* ---------------------------------------------------------------
   Three ways out of the app, as one row rather than three bars.
   Stacked, they were three near-identical pills taking a third of
   the screen to say three words.
   --------------------------------------------------------------- */

.jar-shortcuts {
    display: flex;
    gap: 10px;
    margin-top: 14px;
}

.shortcut {
    /* Equal shares of whatever is left when one of them is missing, rather
       than three fixed columns with a hole in the middle. */
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 72px;
    padding: 8px 6px;
    border-radius: 14px;
    text-decoration: none;
    font-size: var(--fs-label);
    text-align: center;
    transition: background 0.18s ease, border-color 0.18s ease;
}

.shortcut-icon { font-size: 1.25rem; line-height: 1; }
.shortcut-word { line-height: 1.2; }

.call-shortcut, .call-shortcut:visited {
    background: rgba(45, 140, 255, 0.13);
    border: 1px solid rgba(90, 165, 255, 0.4);
    color: #cfe3ff;
}
.call-shortcut:hover { background: rgba(45, 140, 255, 0.26); border-color: #5aa5ff; color: #fff; }

.vault-shortcut, .vault-shortcut:visited {
    background: rgba(60, 190, 130, 0.11);
    border: 1px solid rgba(90, 210, 150, 0.35);
    color: #bfe9d4;
}
.vault-shortcut:hover { background: rgba(60, 190, 130, 0.24); border-color: #5ad296; color: #fff; }

.money-shortcut, .money-shortcut:visited {
    background: rgba(255, 184, 107, 0.12);
    border: 1px solid rgba(255, 184, 107, 0.35);
    color: #ffd9ac;
}
.money-shortcut:hover { background: rgba(255, 184, 107, 0.26); border-color: #ffb86b; color: #fff; }

.shortcut:focus-visible { outline: 2px solid #8a2be2; outline-offset: 3px; }

/* Kept for the lightbox and anywhere else still drawing a pill link. */
.meeting-link {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 18px auto 0;
    padding: 9px 18px;
    border-radius: 999px;
    background: rgba(45, 140, 255, 0.14);
    border: 1px solid rgba(90, 165, 255, 0.45);
    color: #cfe3ff;
    font-size: var(--fs-small);
    text-decoration: none;
    transition: background 0.18s ease, border-color 0.18s ease;
}
.meeting-link:visited { color: #cfe3ff; }
.meeting-link:hover { background: rgba(45, 140, 255, 0.26); border-color: #5aa5ff; color: #fff; }
.meeting-link:focus-visible { outline: 2px solid #8a2be2; outline-offset: 3px; }

/* The vault sits under the call link and does something completely different,
   so it does not get to look the same. Green rather than blue, and quieter:
   this one is a shortcut you reach for occasionally, not the thing the page is
   for. The `.meeting-link` rules above carry the shape; only colour changes. */
.vault-link,
.vault-link:visited {
    margin-top: 10px;
    background: rgba(60, 190, 130, 0.12);
    border-color: rgba(90, 210, 150, 0.4);
    color: #bfe9d4;
    font-size: 0.86rem;
}
.vault-link:hover { background: rgba(60, 190, 130, 0.24); border-color: #5ad296; color: #fff; }

/* Same shape again, gold this time — a link that leaves for money rather
   than for a call or a password. */
.money-link,
.money-link:visited {
    margin-top: 10px;
    background: rgba(255, 184, 107, 0.14);
    border-color: rgba(255, 184, 107, 0.4);
    color: #ffd9ac;
    font-size: 0.86rem;
}
.money-link:hover { background: rgba(255, 184, 107, 0.26); border-color: #ffb86b; color: #fff; }

.meeting-link-icon { font-size: 1.05rem; line-height: 1; }

/* What is coming up, under the links. A list rather than more pills: these are
   things to read, and the pills above are things to press. Quiet on purpose —
   the jar is still what this page is for. */
/* `coming-up` rather than `upcoming`: that one is already a future meeting's
   card in the album, and the two rules met — every coming meeting's card was
   pushed down and pulled in by this box's margins. */
.coming-up {
    margin-top: 14px;
    text-align: start;
    border-radius: 14px;
    background: rgba(255, 255, 255, 0.045);
    border: 1px solid rgba(255, 255, 255, 0.1);
    overflow: hidden;
}

/* A heading over the list, not a control. It was a summary you tapped for one
   afternoon; see the note in the markup for why that came straight back out. */
.coming-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0;
    min-height: 46px;
    padding: 0 16px;
    font-size: var(--fs-body);
    font-weight: 500;
    color: #e6e0f5;
}

.coming-glyph { font-size: 1.1rem; line-height: 1; }

.upcoming-title { margin: 0; font-weight: 500; }

/* The one thing on this page allowed to be a warning colour. */
.coming-flag {
    font-size: var(--fs-label);
    border-radius: 999px;
    padding: 3px 10px;
    background: rgba(255, 107, 129, 0.22);
    color: #ff9aab;
    white-space: nowrap;
}

/* Pushed clear of the heading rather than sitting against it. */
.coming-flag { margin-inline-start: auto; }
/* A habit's line carries its tick beside the link — a button cannot sit
   inside one — so the line is a row of the two. */
.upcoming-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 12px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    transition: background 0.18s ease, border-color 0.18s ease;
}
.upcoming-item:hover { background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 182, 193, 0.45); }
.upcoming-item > .upcoming-link { flex: 1; min-width: 0; }
.upcoming-item.done > .upcoming-link { opacity: 0.55; }
.upcoming-item.done .upcoming-name { text-decoration: line-through; }
.mine-row.mine-habit { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 4px 10px; }
.mine-row.mine-habit > .board-card-facts { grid-column: 2 / -1; }
.mine-row.mine-habit > .mine-open { grid-column: 3; grid-row: 1; }
.mine-row.done { opacity: 0.6; }
.mine-row.done .board-card-title { text-decoration: line-through; }
.habit-panel-tick { display: inline-flex; }

.upcoming-list {
    list-style: none;
    margin: 0;
    padding: 4px 8px 10px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
/* The card is the whole line, so a habit's tick sits inside it and every line
   starts at the same edge whether it has one or not. */
.upcoming-link {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 8px;
    padding: 8px 0;
    color: #e6e0f5;
    font-size: 0.86rem;
    text-decoration: none;
}
.upcoming-link:visited { color: #e6e0f5; }
.upcoming-link:focus-visible { outline: 2px solid #8a2be2; outline-offset: 3px; border-radius: 6px; }
.upcoming-icon { font-size: 0.95rem; line-height: 1; }
.upcoming-name { flex: 1 1 8rem; min-width: 0; overflow-wrap: anywhere; }
.upcoming-when {
    font-size: var(--fs-label);
    border-radius: 999px;
    padding: 1px 8px;
    background: rgba(255, 255, 255, 0.08);
    color: #d8d2e8;
    white-space: nowrap;
}
.upcoming-when.soon { background: rgba(255, 184, 107, 0.2); color: #ffb86b; }
.upcoming-when.late { background: rgba(255, 107, 129, 0.24); color: #ff9aab; }
/* The same light as a board card's owner: the dim purple this used to be all
   but vanished into the page's background, names and all. */
.upcoming-more { font-size: var(--fs-label); color: #d8d2e8; }

@media (prefers-reduced-motion: reduce) {
    .meeting-link { transition: none; }
}

/* Who is in there, inside the call button itself rather than a badge under
   it — one thing to glance at, not two. The dot's colour is the whole story:
   teal and pulsing means Zoom says the room is live right now, still and
   purple means it isn't. The initials next to it answer the second question,
   *who* — a fact the dot alone can't carry once there's more than one of you
   to name. */
.call-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-inline-start: 4px;
    padding-inline-start: 10px;
    border-inline-start: 1px solid rgba(255, 255, 255, 0.18);
    font-size: 0.8rem;
    color: #b9aed4;
}
/* Inside a shortcut tile it sits on its own line under the word, so the rule
   that divided it from the word beside it is a mark with nothing either side
   of it. The pill this was written for is gone. */
.shortcut .call-status {
    margin-inline-start: 0;
    padding-inline-start: 0;
    border-inline-start: none;
}

.call-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #8a2be2;
    box-shadow: 0 0 6px rgba(138, 43, 226, 0.7);
}
.call-dot.live {
    background: #64ffda;
    box-shadow: 0 0 8px rgba(100, 255, 218, 0.8);
    animation: callPulse 2.4s ease-in-out infinite;
}
@keyframes callPulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}
@media (prefers-reduced-motion: reduce) {
    .call-dot.live { animation: none; }
}

/* ---------------------------------------------------------------
   A colour for each of us — see lib/people.js. Always drawn, shown
   only for whoever has the feature. A dot beside a name, never in
   place of it.
   --------------------------------------------------------------- */
:root {
    --amit: #6cb8ff;
    --maria: #c49bff;
}
.person-dot { display: none; }
.with-person-colours .person-dot {
    display: inline-block;
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-inline-end: 5px;
    vertical-align: 0.05em;
}
.with-person-colours .person-dot[hidden] { display: none; }
.with-dot { display: inline-flex; align-items: center; }
.person-dot.person-amit { background: var(--amit); }
.person-dot.person-maria { background: var(--maria); }
/* Both of you: half each, a hard split rather than a blend. */
.person-dot.person-both { background: linear-gradient(90deg, var(--amit) 50%, var(--maria) 50%); }

/*
 * The call's dot: dark and still for a room nobody is in. Outside the
 * person-colours block, because "nobody is in there" is not a fact about
 * either of you, and the dark dot is half of what the word beside it means.
 *
 * `is-empty`, not `empty`: there is a `.empty` utility further down this file
 * for empty-state paragraphs, carrying `margin: 18px 0 0` and a font size, and
 * a dot that matched it was pushed eighteen pixels below the word it belongs
 * beside. The `is-` says this one is a state on an element, not a thing you
 * can put on anything.
 *
 * Nearly black, with a ring. #4a4458 was neither one thing nor the other: too
 * pale to read as "off", too dark to see on the blue tile it sits on. This is
 * an unlit lamp — you can see that it is there and see that it is out.
 */
.call-dot.is-empty {
    background: #0b0714;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.45);
    animation: none;
}
.with-person-colours .call-dot.person-amit { background: var(--amit); box-shadow: 0 0 7px rgba(108, 184, 255, 0.7); }
.with-person-colours .call-dot.person-maria { background: var(--maria); box-shadow: 0 0 7px rgba(196, 155, 255, 0.7); }
.with-person-colours .call-dot.person-both {
    background: linear-gradient(90deg, var(--amit) 50%, var(--maria) 50%);
    box-shadow: 0 0 7px rgba(160, 170, 255, 0.7);
}

/* The editor for it, on the settings page. */
.meeting-edit { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.meeting-edit input { flex: 1 1 260px; min-width: 0; }

/* The test buttons, which only one person gets. Their own block so the lock
   has a heading to sit on rather than landing on a bare row of buttons. */
.notify-tests-block { margin-top: 18px; }

/* On a device count the lock sits inline in a line of small grey text, so it
   has to be quieter than the one on a heading. */
.notify-person small.admin-gated .admin-mark { margin-inline-start: 4px; font-size: 0.9em; }

/* The lock on a panel only one person can open. The same glyph the nav uses,
   so a lock means one thing across the whole site. */
.admin-mark { margin-inline-start: 8px; font-size: 0.8em; opacity: 0.85; }

/* What a page says when it has nothing on it — in its own body, where the
   missing things would be. */
.empty { margin: 18px 0 0; text-align: center; font-size: 0.9rem; color: #8479a0; }

.status { min-height: 1.3em; margin: 8px 0 0; font-size: 0.9rem; color: #a8b2d1; text-align: center; }
.status.ok { color: #64ffda; }
.status.warn { color: #ffb86b; }

.events { list-style: none; margin: 20px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }

.event {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-left: 3px solid #8a2be2;
    border-radius: 10px;
    padding: 14px 16px;
    backdrop-filter: blur(10px);
}

.event.undone { opacity: 0.6; border-left-color: #5a5a70; }

.event-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; font-size: 0.95rem; }
.event-head strong { color: #ffb6c1; font-weight: 600; }
.event-head time { margin-left: auto; font-size: 0.78rem; color: #8479a0; white-space: nowrap; }

.text-before, .text-after {
    display: flex;
    gap: 8px;
    align-items: baseline;
    margin-top: 8px;
    font-size: 0.92rem;
    line-height: 1.5;
}

.text-label {
    flex: none;
    width: 2.4em;
    font-size: var(--fs-label);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #8479a0;
    padding-top: 0.25em;
}

.text-before q { color: #b3a8cd; text-decoration: line-through; text-decoration-color: rgba(255,255,255,0.25); }
.text-after q { color: #fff; }
.text-before q, .text-after q { quotes: none; unicode-bidi: plaintext; }

.event-summary {
    margin: 8px 0 0;
    font-size: 0.9rem;
    color: #d8d2e8;
    line-height: 1.45;
    unicode-bidi: plaintext;
}

.event-foot { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
.event-foot button.ghost { padding: 5px 12px; font-size: 0.82rem; }
.undone-note { font-size: 0.8rem; color: #8479a0; }

#loadMoreBtn { display: block; margin: 20px auto 0; }

.admin-page #authGate { max-width: 420px; margin-left: auto; margin-right: auto; }
.admin-page #whoami { text-align: center; }

@media (max-width: 600px) {
    .page-container { padding: 28px 0 60px; }
    .event-head time { margin-left: 0; width: 100%; }
}

/* ---------------------------------------------------------------
   The todo lists.
   --------------------------------------------------------------- */

.lists-page .page-header { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.lists-page #authGate { max-width: 420px; margin-left: auto; margin-right: auto; }
.lists-page #whoami { text-align: center; }
.lists-page .lang-select { position: static; margin-top: 4px; }

.hint { color: #8479a0; font-size: 0.82rem; margin: 6px 0 0; text-align: center; }
.hint.warn { color: #ffb86b; }
.hint.ok { color: #64ffda; }

/* The per-device notification switch. */
.notify-box {
    margin-top: 26px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
}
.notify-box .hint { max-width: 34ch; line-height: 1.5; }

button.ghost.small { padding: 6px 12px; font-size: 0.82rem; }
button.ghost.tiny, a.ghost.tiny { padding: 3px 8px; font-size: 0.78rem; border-radius: 6px; }

/* The list switcher. Chips wrap rather than scroll — there will never be
   enough lists for a scroller to be worth the hidden-content problem. */
.list-strip {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    margin-top: 24px;
}

.list-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid rgba(255, 255, 255, 0.16);
    color: #d8d2e8;
    box-shadow: none;
    font-weight: normal;
    font-size: 0.9rem;
    padding: 8px 14px;
    border-radius: 999px;
}

.list-chip:hover { background: rgba(255, 255, 255, 0.14); color: #fff; transform: none; }
.list-chip.open {
    background: rgba(138, 43, 226, 0.35);
    border-color: #8a2be2;
    color: #fff;
}
.list-chip.locked { opacity: 0.75; }
.list-chip.archived { opacity: 0.5; font-style: italic; }

.chip-count {
    background: rgba(0, 0, 0, 0.35);
    border-radius: 999px;
    padding: 1px 7px;
    font-size: var(--fs-label);
}
.chip-lock { font-size: 0.8rem; }

/* Arranging the strip.
   The chips stack into a column while this is on, so lib/dragorder.js sees the
   one axis and the even row heights it measures against — see the note on
   `arrangeChip` in views/lists.js for why that is the design and not a
   workaround. */
.arrange-lists { display: block; margin: 12px auto 0; }

.list-strip.arranging {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    max-width: 420px;
    margin-inline: auto;
}

.list-strip.arranging .list-chip {
    justify-content: flex-start;
    cursor: default;
    transition: transform 0.16s ease;
}

/* Nothing opens from here, so nothing should look like it is about to. */
.list-strip.arranging .list-chip:hover {
    background: rgba(255, 255, 255, 0.07);
    color: #d8d2e8;
}
.list-strip.arranging .list-chip.open { background: rgba(138, 43, 226, 0.35); color: #fff; }

.list-chip.dragging {
    border-color: #8a2be2;
    background: rgba(138, 43, 226, 0.18);
    color: #fff;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
    position: relative;
    z-index: 5;
    transition: none;
}
.list-chip.dragging .drag-handle { cursor: grabbing; }

/* Held for one frame while a dropped chip's transform and its real position are
   swapped, so the landing is instant instead of being animated twice. */
.list-strip.settling .list-chip { transition: none; }

@media (prefers-reduced-motion: reduce) {
    .list-strip.arranging .list-chip { transition: none; }
}

.new-list, .new-todo {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
    justify-content: center;
    margin-top: 16px;
}

.new-list input[type="text"],
.new-todo input[type="text"],
.link-form input,
.todo-edit,
.todo-details {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 9px 12px;
    font-family: inherit;
    font-size: 0.95rem;
    outline: none;
    /* Without this a `width: 100%` box is 100% *plus* its padding and border,
       so the notes field hangs 26px past the panel holding it. */
    box-sizing: border-box;
}

.new-list input[type="text"]:focus,
.new-todo input[type="text"]:focus,
.link-form input:focus,
.todo-edit:focus,
.todo-details:focus { border-color: #8a2be2; }

.new-todo { margin-top: 0; }
.new-todo input[type="text"] { flex: 1 1 240px; }

.private-toggle, .archived-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: #a8b2d1;
    font-size: 0.85rem;
    cursor: pointer;
}
.archived-toggle { margin-top: 24px; justify-content: center; width: 100%; }

.list-panel { text-align: left; }

.list-panel-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 16px;
}
.list-panel-head h2 { margin: 0; font-size: 1.15rem; color: #fff; font-weight: 600; }
.counts { font-size: 0.8rem; color: #8479a0; }
.list-panel-actions { margin-left: auto; display: flex; gap: 6px; }

.todos { list-style: none; margin: 18px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.todos[hidden] { display: none; }

/* The pinned sit above the rest with a line between: two lists, each in its
   own order, and the line says where one stops. */
.pinned-list {
    padding-bottom: 12px;
    border-bottom: 1px dashed rgba(255, 182, 193, 0.3);
}
.pinned-list + .todos { margin-top: 12px; }
.todo.pinned { border-color: rgba(255, 182, 193, 0.35); }

/* Off is a grey pin, on is the red one — the same as the padlock beside it. */
.todo-pin { filter: grayscale(1); opacity: 0.55; }
.todo-pin.on { filter: none; opacity: 1; }

/* A film or a show, in front of its title. */
.media-kind { flex: none; font-size: 1.05rem; line-height: 1; }

.todo {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 10px;
    padding: 8px 10px;
}

/* Neighbours slide to open a gap; the dragged row follows the pointer. The
   transition is deliberately not on .dragging, or the row would lag behind
   the finger by its own animation. */
.todo { transition: transform 0.16s ease; }

.todo.dragging {
    border-color: #8a2be2;
    background: rgba(138, 43, 226, 0.18);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
    position: relative;
    z-index: 5;
    transition: none;
}

/* Held for one frame while a dropped row's transform and its real position are
   swapped, so the landing is instant instead of being animated twice. */
.todos.settling .todo { transition: none; }

@media (prefers-reduced-motion: reduce) {
    .todo { transition: none; }
}

.todo-row { display: flex; align-items: center; gap: 10px; }

.drag-handle {
    flex: none;
    border-radius: 6px;
    color: #6f6790;
    cursor: grab;
    font-size: 1.1rem;
    line-height: 1;
    padding: 4px 2px;
    /* Without this the browser claims the gesture for scrolling and the drag
       never starts on a phone. */
    touch-action: none;
}
.todo.dragging .drag-handle { cursor: grabbing; }
.drag-handle:focus-visible { outline: 2px solid #8a2be2; outline-offset: 2px; }

.todo-tick { flex: none; width: 20px; height: 20px; accent-color: #8a2be2; cursor: pointer; }

.todo-title {
    flex: 1;
    font-size: 0.98rem;
    line-height: 1.45;
    cursor: text;
    unicode-bidi: plaintext;
    /* Long unbroken links pasted as titles must not widen the page. */
    overflow-wrap: anywhere;
}
.todo-title:focus-visible { outline: 2px solid #8a2be2; outline-offset: 3px; border-radius: 4px; }

.todo.done { opacity: 0.55; }
.todo.done .todo-title { text-decoration: line-through; text-decoration-color: rgba(255,255,255,0.3); }

.todo-edit { flex: 1; }
.todo-actions { flex: none; display: flex; gap: 4px; align-items: center; }

.todo-detail {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px 4px 6px 4px;
    margin-top: 8px;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.todo-details { width: 100%; resize: vertical; line-height: 1.5; }

.attachments { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 10px; }

.attachment {
    display: flex;
    align-items: center;
    gap: 8px;
    background: rgba(0, 0, 0, 0.25);
    border-radius: 8px;
    padding: 6px 10px;
    font-size: 0.88rem;
    max-width: 100%;
}
.attachment a { color: #ffb6c1; overflow-wrap: anywhere; }
.attachment.image { flex-direction: column; align-items: flex-start; }

/* Standing in for a link that is still on its way. A quiet pulse rather than a
   spinner: it is one line of text, and a spinner per link would be a fidget. */
.attachment.skeleton {
    pointer-events: none;
    padding: 0;
    background: none;
}
.attachment.skeleton span {
    display: block;
    width: 132px;
    height: 29px;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.07);
    animation: pulseSync 1.4s ease-in-out infinite;
}

@keyframes pulseSync {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
    .attachment.skeleton span { animation-duration: 4s; }
}

/* The pictures on a todo, in the same uniform grid the album uses. Narrower
   columns: a detail panel is not a gallery, and these are usually a photo of a
   receipt or a screenshot rather than something to linger over. */
.pic-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    gap: 8px;
}
.pic-grid:empty { display: none; }

/* What a row is carrying, said before you open it. */
.carries { font-size: 0.8rem; margin-left: 3px; line-height: 1; }
.carries-count {
    margin-left: 4px;
    font-variant-numeric: tabular-nums;
    opacity: 0.75;
}

/* The thumbnail is a button, so it needs stripping back to being a picture. */
.thumb {
    padding: 0;
    border: none;
    background: none;
    box-shadow: none;
    border-radius: 8px;
    cursor: zoom-in;
    line-height: 0;
    display: block;
}
.thumb:hover { transform: none; }
.thumb:hover img { filter: brightness(1.08); }
.thumb:focus-visible { outline: 2px solid #8a2be2; outline-offset: 3px; }

.attachment img {
    max-width: min(320px, 100%);
    max-height: 320px;
    border-radius: 8px;
    display: block;
}

/* ---------------------------------------------------------------
   The notification switches.
   --------------------------------------------------------------- */

.settings-heading {
    margin: 24px 0 6px;
    font-size: 1.3rem;
    color: #fff;
    font-weight: 600;
}

.settings-lede {
    color: #a8b2d1;
    font-size: 0.9rem;
    line-height: 1.6;
    max-width: 62ch;
    margin: 0 auto 18px;
}

.settings-panel { text-align: left; margin-bottom: 16px; }

.settings-subheading {
    margin: 0 0 10px;
    font-size: 0.78rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #8479a0;
    font-weight: 600;
}

/* Was a footer box on every page; now it is the first thing in Settings, so it
   sits left with everything else rather than centring itself. */
.settings-panel .notify-box { margin-top: 0; align-items: flex-start; }
.settings-panel .notify-box .hint { text-align: left; max-width: 46ch; }

.notify-table {
    width: 100%;
    border-collapse: collapse;
    text-align: left;
}

.notify-table th, .notify-table td {
    padding: 10px 8px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    vertical-align: top;
}

.notify-table tr:first-child th {
    border-bottom-color: rgba(255, 255, 255, 0.18);
    font-size: 0.78rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #8479a0;
}

/* The two name columns are narrow and fixed; the description takes the rest. */
.notify-person { width: 92px; text-align: center; }
.notify-person span { display: block; color: #fff; text-transform: none; letter-spacing: 0; font-size: 0.95rem; }
.notify-person small { display: block; color: #6f6790; font-size: var(--fs-label); letter-spacing: 0; text-transform: none; }
.notify-person small.none { color: #ffb86b; }

.notify-what { font-weight: normal; }
.notify-label { display: block; color: #d8d2e8; font-size: 0.95rem; }
.notify-what small { display: block; color: #8479a0; font-size: 0.8rem; line-height: 1.5; margin-top: 2px; }

.notify-table td { text-align: center; }
.notify-table input[type="checkbox"] {
    width: 18px;
    height: 18px;
    accent-color: #8a2be2;
    cursor: pointer;
}

.notify-tests { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 20px; }

/* ------------------------------------------------------------- contacts */

.contacts-body { display: flex; flex-direction: column; gap: 30px; text-align: left; }

.contact-section-heading {
    margin: 0 0 12px;
    font-size: 0.78rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #8479a0;
    font-weight: 600;
}

.contact-card {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    padding: 14px 16px;
    margin-bottom: 12px;
}

/* The one card that is actually them, rather than somebody they know. */
.contact-card.self { border-color: rgba(255, 182, 193, 0.35); }

.contact-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.contact-head h3 { margin: 0; font-size: 1.05rem; color: #fff; }
.contact-relation { color: #8479a0; font-size: 0.85rem; }
.contact-tools { margin-inline-start: auto; display: flex; gap: 4px; }

.contact-field {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 7px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.field-label {
    flex: none;
    width: 88px;
    color: #6f6790;
    font-size: var(--fs-label);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.field-value { flex: 1; color: #d8d2e8; overflow-wrap: anywhere; margin: 0; }

/* A number or an address you can tap. It has to look the same as the field it
   replaced, so :visited is pinned too — otherwise ringing somebody once turns
   their number purple for good. */
a.field-value,
a.field-value:visited { color: #d8d2e8; text-decoration: none; }
a.field-value:hover,
a.field-value:focus-visible { color: #fff; text-decoration: underline; }

/* The button this page exists for. Kept quiet until you are near it. */
.copy { flex: none; }
.copy.copied { color: #64ffda; border-color: rgba(100, 255, 218, 0.5); opacity: 1; }

.contact-addresses { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }

.contact-address {
    background: rgba(0, 0, 0, 0.22);
    border-radius: 8px;
    padding: 8px 10px;
}
.address-head { display: flex; align-items: center; gap: 6px; }

/* An anchor that sits in a row of buttons. The size and the frame come from
   `a.ghost.tiny` alongside the real ones; only the parts an anchor does not
   get for free are here. */
.map-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
}

/* One height for every small control on a card, set by a *number* rather than
   left to `normal`. `normal` derives the line box from the metrics of whatever
   font ends up drawing the glyph, so a colour emoji and a text glyph in
   otherwise identical boxes come out different heights. A number does not
   depend on the glyph at all, which is the only way these four line up. */
.contact-card .ghost.tiny { line-height: 1.45; }
/* Pinned against the browser's own link colours: unstyled, this went blue and
   then stayed purple once opened, both of which vanish into the background.
   Same value the neighbouring buttons take from `.ghost`. */
.map-link, .map-link:visited { color: #d8d2e8; }
.map-link:hover, .map-link:focus-visible { color: #fff; }

.address-label { flex: 1; color: #ffb6c1; font-size: 0.85rem; font-weight: 600; }
.address-text { margin: 4px 0 0; color: #d8d2e8; white-space: pre-wrap; overflow-wrap: anywhere; }

.contact-form, .address-form {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}
.contact-form input, .address-form input {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 9px 12px;
    font-family: inherit;
    font-size: 0.95rem;
    outline: none;
    box-sizing: border-box;
    flex: 1 1 160px;
}
.contact-form input:focus, .address-form input:focus { border-color: #8a2be2; }
/* The address itself is the long one, and wants a line of its own. */
.address-text-input { flex: 1 1 100%; }
.new-contact { margin-top: 10px; }

@media (max-width: 600px) {
    .field-label { width: 100%; }
    .contact-field { flex-wrap: wrap; }
}

/* -------------------------------------------------- the devices signed up */

.notify-devices { display: flex; flex-direction: column; gap: 22px; }

.device-group h4 {
    margin: 0 0 8px;
    font-size: 0.95rem;
    font-weight: 600;
    color: #fff;
}

.device-list { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 8px; }

.device {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.22);
}

.device-what { flex: 1; min-width: 0; }
.device-label { display: block; color: #d8d2e8; font-size: 0.9rem; }
.device small { display: block; color: #8479a0; font-size: 0.78rem; margin-top: 2px; }

/* Square, so it reads as a close rather than as a word cut short. */
.device-forget {
    flex: none;
    width: 28px;
    height: 28px;
    padding: 0;
    line-height: 1;
    color: #8479a0;
}
.device-forget:hover:not(:disabled) { color: #ff9d9d; border-color: rgba(255, 120, 120, 0.6); }

/* Never reached, or failing. The one thing worth spotting in this list. */
.device.suspect { border-color: rgba(255, 184, 107, 0.45); }
.device.suspect small { color: #ffb86b; }

.device-group .danger { border-color: rgba(255, 120, 120, 0.4); color: #ff9d9d; }
.device-group .danger:hover:not(:disabled) { border-color: rgba(255, 120, 120, 0.75); }

@media (max-width: 600px) {
    .notify-table th, .notify-table td { padding: 8px 4px; }
    .notify-person { width: 64px; }
    .notify-what small { display: none; }
}

/* ---------------------------------------------------------------
   The kit.
   --------------------------------------------------------------- */

.kit-filters-label { margin: 0 0 6px; }

.kit-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 20px;
}

.kit-filter {
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid rgba(255, 255, 255, 0.16);
    color: #d8d2e8;
    box-shadow: none;
    font-weight: normal;
    font-size: 0.9rem;
    padding: 7px 18px;
    border-radius: 999px;
}
.kit-filter:hover { background: rgba(255, 255, 255, 0.14); color: #fff; transform: none; }
/* A translucent purple used to mark this one as selected, but this page's
   own background is purple too — against it, "on" and "off" read as almost
   the same chip. Solid and pink instead, the same strong fill this app's
   buttons already use for "this is the one that's active/primary" — no
   background this page has is going to wash that out. */
.kit-filter.on, .kit-filter.on:hover {
    background: #ffb6c1;
    border-color: #ffb6c1;
    color: #1e1e2f;
    font-weight: 600;
}

.kit-form input[type="text"] { flex: 1 1 200px; }

.kit-assignee {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 9px 10px;
    font-family: inherit;
    font-size: 0.9rem;
}

.kit-panel { text-align: left; margin-top: 18px; }

.kit-head { display: flex; align-items: baseline; gap: 12px; min-height: 28px; }
.kit-head .counts { flex: 1; }

.kit-list { margin-top: 12px; }

.kit-item {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-left: 3px solid var(--priority-colour, transparent);
    border-radius: 10px;
    padding: 8px 10px;
    transition: transform 0.16s ease;
}

/* Priority is a stripe down the edge rather than a badge in the row: it has to
   be readable at a glance while scanning, and it is not what you tap.
 *
 * One declaration serves both the stripe on a row and the swatch in the legend
 * that explains it, so the two cannot drift apart — a legend that has quietly
 * stopped matching the list is worse than no legend.
 *
 * Only `important` is allowed to shout. The rest step down in order, which is
 * the point: what is loud should be the short list. */
.kit-item.important, .kit-legend-row.important { --priority-colour: #ff6b9d; }
.kit-item.normal, .kit-legend-row.normal { --priority-colour: #82aaff; }
.kit-item.optional, .kit-legend-row.optional { --priority-colour: #6a5f88; }
.kit-item.basic, .kit-legend-row.basic { --priority-colour: rgba(255, 255, 255, 0.07); }

.kit-item.optional { opacity: 0.85; }

/* Something the other one let out that you haven't packed yet.
 *
 * Loud on purpose. The first attempt was a faint wash and it was nearly
 * invisible against a list of sixty-five rows, which made it decoration rather
 * than a marker — the whole point is that it catches the eye of somebody who
 * missed the confetti.
 *
 * The left edge is left alone: that is the priority stripe, and it has to stay
 * readable. So the ring is drawn on the other three sides and the glow does the
 * rest. It all goes when the thing is packed — see revealedForYou in
 * views/kit.js. */
.kit-item.let-out {
    background:
        linear-gradient(90deg, rgba(255, 182, 193, 0.30), rgba(255, 182, 193, 0.07) 70%);
    border-top-color: rgba(255, 182, 193, 0.55);
    border-right-color: rgba(255, 182, 193, 0.55);
    border-bottom-color: rgba(255, 182, 193, 0.55);
    box-shadow: 0 0 16px rgba(255, 182, 193, 0.22);
}

/* A badge rather than a bare glyph: at 0.85rem with no background it read as
   punctuation. One rule for all three places it turns up — a kit row somebody
   let out, a list row that is out, and a revealed row in a padlocked panel —
   so the same thing looks the same wherever you meet it.
   
   The eye is *only* ever this. Every control over whether the other one can
   see something is a padlock; see the note in views/lists.js. */
.kit-let-out,
.todo-let-out,
.revealed-list .reveal-mark {
    flex: none;
    color: #ffe3e8;
    background: rgba(255, 182, 193, 0.28);
    border-radius: 999px;
    padding: 3px 9px;
    font-size: 0.9rem;
    line-height: 1;
}

/* The everyday things. Nobody but you sees these rows, and there are usually a
   lot of them, so they read as background rather than as a list to work
   through. */
.kit-item.basic { opacity: 0.72; }
.kit-item.basic .kit-title { font-size: 0.92rem; }
.kit-priority.basic { opacity: 0.55; }

.kit-item.dragging {
    border-color: #8a2be2;
    background: rgba(138, 43, 226, 0.18);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
    position: relative;
    z-index: 5;
    transition: none;
}
.kit-list.settling .kit-item { transition: none; }

@media (prefers-reduced-motion: reduce) {
    .kit-item { transition: none; }
}

.kit-row { display: flex; align-items: center; gap: 10px; }
.kit-tick { flex: none; width: 20px; height: 20px; accent-color: #8a2be2; cursor: pointer; }

.kit-title {
    flex: 1;
    font-size: 0.98rem;
    line-height: 1.45;
    cursor: text;
    unicode-bidi: plaintext;
    overflow-wrap: anywhere;
}
.kit-title:focus-visible { outline: 2px solid #8a2be2; outline-offset: 3px; border-radius: 4px; }

.kit-edit {
    flex: 1;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 7px 10px;
    font-family: inherit;
    font-size: 0.95rem;
    outline: none;
}
.kit-edit:focus { border-color: #8a2be2; }

/* Packed, not gone: struck through and dimmed, still there to change your mind
   about, and moved to the end of its own group by the view. */
.kit-item.packed { opacity: 0.5; }
.kit-item.packed .kit-title { text-decoration: line-through; text-decoration-color: rgba(255,255,255,0.35); }

.kit-actions { flex: none; display: flex; gap: 4px; align-items: center; }

/* Dropdowns rather than tap-to-cycle: three answers is one too many to guess
   your way around. Styled down to the size of the buttons they replaced. */
.kit-select {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: #d8d2e8;
    border-radius: 6px;
    padding: 3px 4px;
    font-family: inherit;
    font-size: 0.78rem;
    outline: none;
    cursor: pointer;
    max-width: 96px;
}
.kit-select:hover { border-color: rgba(255, 255, 255, 0.35); color: #fff; }
.kit-select:focus { border-color: #8a2be2; }
.kit-select option { background: #1a1030; color: #d8d2e8; }

.kit-who.nobody { opacity: 0.6; }

/* A secret is still an item on the list — same row, same tick, same place in
   the order. Only its words are missing.
 *
 * It used to say so with a cyan left border, which stopped working twice over:
 * that border now carries the priority, so a secret both lost its own colour
 * and had to compete with three others for attention. The hatch is the marker
 * instead — nothing else on this page uses one, so it reads as "sealed" at a
 * glance and costs the stripe nothing. */
/* One rule for a secret on the kit and on a list, so the two cannot drift
 * apart again. They did once: the kit got this hatch and the lists kept a
 * dashed border at a tenth of white that nobody could see. */
.kit-item.secret,
.todo.secret {
    background-image: repeating-linear-gradient(
        135deg,
        rgba(100, 255, 218, 0.08) 0 5px,
        rgba(100, 255, 218, 0) 5px 11px
    );
    border-color: rgba(100, 255, 218, 0.38);
}
/* After `border-color`, so the priority keeps its own stripe. */
.kit-item.secret { border-left-color: var(--priority-colour, transparent); }

.kit-title.hidden-title,
.todo-title.hidden-title {
    /* Teal rather than grey: it is sealed, not disabled. */
    color: #7fbfb2;
    font-style: italic;
    cursor: default;
    letter-spacing: 0.02em;
}
/* The open padlock grey and faint on every ordinary row, so the one that is
   shut is the one you notice. */
.kit-hide, .todo-hide { filter: grayscale(1); opacity: 0.55; }
.kit-hide.on, .todo-hide.on { filter: none; opacity: 1; }

.kit-secret-toggle { white-space: nowrap; }

/* Sits under the filters, quiet: it is a "let me look at my own pile" switch,
   not one of the three things the page is actually organised by. */
.kit-basics-toggle {
    margin: 4px 0 20px;
    justify-content: flex-start;
    width: auto;
    font-size: 0.8rem;
    opacity: 0.75;
}
.kit-basics-toggle:hover { opacity: 1; }
.kit-priority.important { color: #ff6b9d; border-color: rgba(255, 107, 157, 0.4); }
.kit-priority.optional { opacity: 0.65; }
.kit-remove:hover { color: #ff6b9d; }

/* -------------------------------------------- a secret, no longer a secret
 *
 * A dialog rather than a positioned overlay, like the lightbox: showModal()
 * puts it in the browser's top layer, which is above everything on the page
 * without anybody having to pick a z-index and hope.
 */
.reveal {
    border: none;
    padding: 0;
    background: transparent;
    max-width: 100vw;
    max-height: 100dvh;
    margin: auto;
}
.reveal::backdrop { background: rgba(8, 3, 18, 0.72); backdrop-filter: blur(4px); }

.reveal-box {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 28px 32px;
    border-radius: 18px;
    background: rgba(30, 20, 55, 0.96);
    border: 1px solid rgba(255, 182, 193, 0.4);
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
    text-align: center;
    animation: revealIn 0.42s cubic-bezier(0.2, 1.3, 0.4, 1);
}

.reveal-cheer { margin: 0; font-size: 2.6rem; line-height: 1; animation: revealPop 0.9s ease; }
.reveal-box h2 { margin: 0; font-size: 1.15rem; color: #ffb6c1; }
.reveal-who { margin: 0; font-size: 0.85rem; color: #a8b2d1; }

.reveal-names {
    list-style: none;
    margin: 4px 0 6px;
    padding: 0;
    display: grid;
    gap: 6px;
    /* Room for the longest present anybody is going to name, without the box
       stretching to the width of a laptop for one short word. */
    min-width: min(240px, 60vw);
}

/* Four or more and the box would push its own dismiss button off a phone.
   Scrolling rather than a "…and 3 more": a count you have to trust is worse
   than a list you can read. */
.reveal-names.many {
    max-height: min(46dvh, 320px);
    overflow-y: auto;
    /* So the last row doesn't look like the end when it isn't. */
    padding-inline-end: 4px;
}

.reveal-names li {
    display: flex;
    flex-direction: column;
    gap: 1px;
    font-size: 1rem;
    color: #fff;
    background: rgba(255, 255, 255, 0.07);
    border-radius: 8px;
    padding: 6px 14px;
    overflow-wrap: anywhere;
    unicode-bidi: plaintext;
    /* `both`, so a name waits out its delay hidden rather than flashing into
       place and then animating. The delay itself is set per row in
       lib/celebrate.js — it is a count, not a constant. */
    animation: revealName 0.42s cubic-bezier(0.2, 1.3, 0.4, 1) both;
}

/* Which list it came off. Quieter than the name, and never competing with it. */
.reveal-note { font-size: var(--fs-label); color: #a8b2d1; }

@keyframes revealIn {
    from { opacity: 0; transform: scale(0.86) translateY(12px); }
    to { opacity: 1; transform: none; }
}
@keyframes revealPop {
    0% { transform: scale(0.4) rotate(-12deg); }
    60% { transform: scale(1.18) rotate(6deg); }
    100% { transform: none; }
}
@keyframes revealName {
    from { opacity: 0; transform: translateY(10px) scale(0.96); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .reveal-box, .reveal-cheer { animation: none; }
    /* Every name at once, and none of them left waiting on a delay that no
       longer runs — `both` would otherwise hold the last ones invisible. */
    .reveal-names li { animation: none; }
}

/* ------------------------------------------------- what the stripes mean
 *
 * Folded shut by default. Four levels are only obvious to whoever chose the
 * words, and the answer to that is a legend you can open once — not four
 * labels permanently taking up room in every row. */
.kit-legend {
    margin: 0 0 14px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 10px;
    background: rgba(0, 0, 0, 0.18);
}

.kit-legend summary {
    padding: 8px 12px;
    cursor: pointer;
    font-size: 0.8rem;
    color: #a8b2d1;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 8px;
}
.kit-legend summary::-webkit-details-marker { display: none; }
.kit-legend summary:hover { color: #fff; }
/* The triangle, drawn rather than inherited, so it turns with the panel in
   both writing directions. */
.kit-legend summary::before {
    content: "›";
    font-size: 1rem;
    line-height: 1;
    transition: transform 0.15s ease;
}
.kit-legend[open] summary::before { transform: rotate(90deg); }

.kit-legend-list {
    list-style: none;
    margin: 0;
    padding: 0 12px 12px;
    display: grid;
    gap: 10px;
}

.kit-legend-row {
    display: grid;
    grid-template-columns: 4px 1fr;
    gap: 10px;
    align-items: start;
}
/* The same stripe a row wears, stood on end and given room to be seen. */
.kit-legend-row::before {
    content: "";
    align-self: stretch;
    min-height: 2.2em;
    border-radius: 2px;
    background: var(--priority-colour);
}
.kit-legend-row strong {
    display: block;
    color: #fff;
    font-size: 0.85rem;
    font-weight: 600;
}
.kit-legend-row span { color: #8479a0; font-size: 0.78rem; line-height: 1.45; }

@media (prefers-reduced-motion: reduce) {
    .kit-legend summary::before { transition: none; }
}

@media (max-width: 600px) {
    .kit-panel { padding: 16px 12px; }
    /* The buttons wrap under the name rather than squeezing it to nothing. */
    .kit-row { flex-wrap: wrap; }
    .kit-title { flex: 1 1 100%; order: -1; padding-left: 30px; }
    .kit-actions { margin-left: auto; }
}

/* ---------------------------------------------------------------
   Flights on a meeting.
   --------------------------------------------------------------- */

/* The forecast. Read at a glance while looking at the kit, so it is a row of
   small columns rather than a table of numbers. */
.meeting-weather {
    margin: 18px 0;
    padding: 12px 14px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    background: rgba(0, 0, 0, 0.18);
}

.weather-days {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    justify-content: flex-start;
}

/* A trip with cities on it stacks one forecast per city. The days of two
   cities laid end to end read as one place having a fortnight of weather,
   which is the thing a heading per city exists to prevent. */
.weather-days.by-city { flex-direction: column; gap: 16px; }
.weather-city-name {
    margin: 0 0 6px;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: #b9aed4;
}
.weather-city .hint { margin: 4px 0 0; font-size: var(--fs-label); text-align: left; }

.weather-day {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    min-width: 58px;
}

.weather-when { font-size: var(--fs-label); color: #8479a0; text-transform: uppercase; letter-spacing: 0.06em; }
.weather-date { font-size: var(--fs-label); color: #6f6790; white-space: nowrap; font-variant-numeric: tabular-nums; }
.weather-sky { font-size: 1.3rem; line-height: 1.2; }
.weather-degrees { font-size: 0.82rem; color: #d8d2e8; font-variant-numeric: tabular-nums; }
.weather-rain { font-size: var(--fs-label); color: #64b5ff; font-variant-numeric: tabular-nums; }

.meeting-weather .hint { text-align: left; margin: 0; }

.meeting-flights {
    margin: 18px 0;
    padding: 14px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    background: rgba(0, 0, 0, 0.18);
    text-align: left;
}

.flights { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }

.flight {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 12px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
}

.flight-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }

/* ---------------------------------------------------------------
   Where we're staying. Deliberately the same shape as the flights panel
   above it — they answer the two halves of the same question.
   --------------------------------------------------------------- */

/* ---------------------------------------------------------------
   The cities a trip visits. Deliberately the same panel as the
   stays and the flights below — they are three answers to the same
   question, and the itinerary is the one the others hang off.
   --------------------------------------------------------------- */

.meeting-legs {
    margin: 18px 0;
    padding: 14px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    background: rgba(0, 0, 0, 0.18);
    text-align: left;
}

.legs { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 8px; }

.leg {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
}

.leg-text { flex: 1; min-width: 0; }
.leg-where { color: #ece8f5; font-size: 0.95rem; overflow-wrap: anywhere; }
.leg-when { color: #b9aed4; font-size: 0.8rem; font-variant-numeric: tabular-nums; }
.leg-nights { color: #8479a0; font-size: var(--fs-label); }

.meeting-legs .hint { margin: 0 0 10px; font-size: var(--fs-label); text-align: left; }

.leg-form { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; position: relative; }
.leg-form-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-end; }

#legCity {
    width: 100%;
    box-sizing: border-box;
    padding: 8px 10px;
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.25);
    color: #ece8f5;
    font: inherit;
    font-size: 0.9rem;
    unicode-bidi: plaintext;
}
#legCity:focus { outline: none; border-color: #8a2be2; }

.leg-suggestions {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    text-align: start;
}
.leg-suggestions button { width: 100%; text-align: start; unicode-bidi: plaintext; }

/* ---------------------------------------------------------------
   A train, as a row among the flights. No panel of its own: most trips
   have no train, and a heading with an empty list under it charges
   every meeting for the ones that do.

   One difference from a flight row earns its place — the times are the
   biggest thing on it. On the morning you travel this is read from two
   feet away, and everything else on it is detail.
   --------------------------------------------------------------- */

.train {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 12px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
}

.train-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }
.train-name { font-weight: 700; color: #ece8f5; letter-spacing: 0.02em; }
/* Where a flight row says OUT or BACK, a train row wears this. Deliberately not
   `.flight-leg` itself: that rule is 0.72rem uppercase letter-spacing, which is
   right for a word and shrinks an emoji to a smudge. */
.train-mark { font-size: 1rem; line-height: 1; }

/* Pushed to the far end of the line it shares, away from everything else. */
.train-remove { margin-inline-start: auto; }
.train-changes { font-size: var(--fs-label); }

.train-when {
    font-size: 1.15rem;
    font-weight: 600;
    color: #f3f0fa;
    /* Lining figures, so 09:22 and 12:17 stack to the same width in a list. */
    font-variant-numeric: tabular-nums;
}
.train-day { font-size: var(--fs-label); font-weight: 400; }

.train-route { color: #d6cfe8; font-size: 0.9rem; overflow-wrap: anywhere; }
.train-platform, .train-summary { font-size: 0.8rem; color: #b9aed4; }
.train-summary { font-variant-numeric: tabular-nums; }
.train-problem { text-align: left; margin: 2px 0 0; }

.train-legs {
    list-style: none;
    margin: 2px 0 0;
    padding: 0 0 0 10px;
    border-inline-start: 2px solid rgba(255, 255, 255, 0.12);
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: var(--fs-label);
    font-variant-numeric: tabular-nums;
}

.train-tickets {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.train-ticket { display: flex; align-items: center; gap: 4px; }

/* A link, dressed as one. The same dashed underline `a.stay-name` wears, so
   the two things in this app that open somebody else's document look alike —
   and so this stops looking like the buttons beside it, which do things here
   rather than going anywhere. */
.train-ticket-link {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    max-width: 14rem;
    color: #ece8f5;
    font-size: 0.85rem;
    text-decoration: none;
    border-bottom: 1px dashed rgba(255, 182, 193, 0.45);
}
.train-ticket-link:hover { color: #ffb6c1; border-bottom-color: #ffb6c1; }
.ticket-mark { flex: none; font-size: 0.9em; }
/* The filename is the only thing telling two passengers' tickets apart, so it
   ellipses rather than wrapping the row. */
.ticket-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.train-details {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 6px;
    padding-top: 8px;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.train-details .mini-field { flex: 1 1 6rem; }
.train-details textarea { min-height: 3.5em; resize: vertical; }
.train-details input, .train-details textarea {
    width: 100%;
    box-sizing: border-box;
    padding: 6px 8px;
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.25);
    color: #ece8f5;
    font: inherit;
    font-size: 0.85rem;
    unicode-bidi: plaintext;
}
.train-details input:focus, .train-details textarea:focus { outline: none; border-color: #8a2be2; }

.train-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }

/* The two station boxes, in the flights form. They take a name rather than a
   code, so they need the room a flight number does not — and they wrap onto
   their own line on a phone rather than squeezing the date off the end. */
.train-station {
    flex: 1 1 11rem;
    min-width: 0;
    unicode-bidi: plaintext;
}

.train-form-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-end; }

.train-results {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
/* Two lines in one button: when it goes, and what it is. The first is what
   the eye lands on, so it is the one given the room. */
.train-result {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    text-align: start;
}
.train-result-when { font-weight: 600; font-variant-numeric: tabular-nums; }
.train-result-what { font-size: var(--fs-label); }

.train-typed { display: flex; flex-direction: column; gap: 8px; }

.meeting-stays, .meeting-places {
    margin: 18px 0;
    padding: 14px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    background: rgba(0, 0, 0, 0.18);
    text-align: left;
}

/* ------------------------------------------------------------- sections
   Each part of a meeting folds to its heading. The marker is drawn rather
   than left to the browser, whose triangle differs everywhere and cannot be
   coloured to match; it turns a quarter when the section is open. */
.meeting-album {
    margin: 18px 0;
    padding: 14px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    background: rgba(0, 0, 0, 0.18);
    text-align: left;
}
details.meeting-section[hidden] { display: none; }
.section-summary {
    display: flex;
    align-items: center;
    gap: 8px;
    list-style: none;
    cursor: pointer;
    user-select: none;
    min-height: 32px;
}
.section-summary::-webkit-details-marker { display: none; }
.section-summary::before {
    content: "▸";
    color: #b9aed4;
    font-size: var(--fs-small);
    transition: transform 0.15s ease;
}
details[open] > .section-summary::before { transform: rotate(90deg); }
/* Right to left, a closed section points the way the text runs. */
[data-dir="rtl"] .section-summary::before { content: "◂"; }
[data-dir="rtl"] details[open] > .section-summary::before { transform: rotate(-90deg); }
details[open] > .section-summary { margin-bottom: 10px; }
.section-summary .stays-heading { margin: 0; }
.section-count {
    color: #8479a0;
    font-size: var(--fs-label);
    font-variant-numeric: tabular-nums;
}
.section-summary:focus-visible { outline: 2px solid #8a2be2; outline-offset: 3px; border-radius: 6px; }
@media (prefers-reduced-motion: reduce) {
    .section-summary::before { transition: none; }
}

.stays-heading {
    margin: 0 0 10px;
    font-size: 0.82rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #b9aed4;
}

.stays { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 10px; }

.stay {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 12px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
    /* Which kind of place it is, read before any of the words are. */
    border-inline-start: 3px solid var(--stay-colour, rgba(255, 255, 255, 0.2));
}
.stay.airbnb  { --stay-colour: #ff6b81; }
.stay.hotel   { --stay-colour: #5aa5ff; }
.stay.friends { --stay-colour: #64ffda; }
.stay.other   { --stay-colour: #b9aed4; }
/* Reused for places of interest — same row, same rule, four more kinds. */
.stay.restaurant { --stay-colour: #ffb86b; }
.stay.cafe        { --stay-colour: #d2a679; }
.stay.bar         { --stay-colour: #ff9ecb; }
.stay.sight       { --stay-colour: #8a2be2; }

.stay-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }
.stay-mark { font-size: 1rem; line-height: 1; }

.stay-name { color: #fff; font-size: 0.95rem; font-weight: 600; }
a.stay-name { text-decoration: none; border-bottom: 1px dashed rgba(255, 182, 193, 0.45); }
a.stay-name:hover { color: #ffb6c1; border-bottom-color: #ffb6c1; }

.stay-when {
    margin: 0;
    font-size: 0.82rem;
    color: #ffb6c1;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.stay-hours {
    margin: 2px 0 0;
    font-size: 0.78rem;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px;
}

.stay-hours-value {
    color: #d8d2e8;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.stay-address, .stay-door {
    margin: 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 0.85rem;
    color: #d8d2e8;
}

.stay-label {
    font-size: var(--fs-label);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #8479a0;
}

/* A door code is read off a screen at a door. It gets the room to be legible
   at arm's length, and the spacing so 0 and O are not the same shape. */
.stay-code {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 1rem;
    letter-spacing: 0.12em;
    color: #fff;
    background: rgba(255, 255, 255, 0.08);
    border-radius: 6px;
    padding: 2px 8px;
}

.stay-notes {
    margin: 2px 0 0;
    font-size: 0.85rem;
    line-height: 1.5;
    color: #b9aed4;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.stay-actions { display: flex; gap: 6px; margin-top: 6px; }

/* ------------------------------------------------------------ the form */

.stay-form {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.1);
}

/* Two to a line where there is room, stacked on a phone. */
.stay-form-row { display: flex; flex-wrap: wrap; gap: 8px; }
.stay-form-row > * { flex: 1 1 160px; min-width: 0; }

.stay-field { display: flex; flex-direction: column; gap: 4px; }
.stay-field > span {
    font-size: var(--fs-label);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #8479a0;
}

.stay-form input, .stay-form select, .stay-form textarea { width: 100%; }
.stay-form-buttons { display: flex; gap: 8px; }

/* "Stayed here before": a button per place from an earlier trip, with what it
   takes to recognise it — a picture or its mark, the name, the address, and
   when you were last there. A button, so it has to undo the big pink one. */
.stay-before { display: flex; flex-direction: column; gap: 6px; }
.stay-before-title {
    margin: 0;
    font-size: var(--fs-label);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #8479a0;
}
.stay-before-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.stay-before-pick {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    text-align: start;
    font-weight: normal;
    color: #e6e0f5;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 10px;
    box-shadow: none;
}
.stay-before-pick:hover { background: rgba(255, 255, 255, 0.09); border-color: rgba(255, 182, 193, 0.45); transform: none; }
.stay-before-pick.chosen { border-color: #64ffda; background: rgba(100, 255, 218, 0.08); }
.stay-before-thumb {
    flex: none;
    width: 52px;
    height: 52px;
    border-radius: 8px;
    object-fit: cover;
}
.stay-before-mark {
    flex: none;
    width: 52px;
    height: 52px;
    display: grid;
    place-items: center;
    font-size: 1.4rem;
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.25);
}
.stay-before-words { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.stay-before-words strong { color: #fff; font-size: 0.9rem; }
.stay-before-address { font-size: 0.78rem; color: #cfc7e3; overflow-wrap: anywhere; }
.stay-before-when { font-size: var(--fs-label); color: #a8b2d1; }

/* The picture of a place, beside what is written about it. A photo's own
   shape rather than a square, so nothing of the room is cropped away, and big
   enough to recognise it by — which is the whole point of having it. */
/* What the row says, as one stack — the row itself becomes two columns when
   there is a picture, so the words need a column of their own. */
.stay-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }

.stay.with-picture {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 10px 12px;
    align-items: start;
}
.stay.with-picture > .stay-text { min-width: 0; }
.stay.with-picture > .stay-actions { grid-column: 1 / -1; }
.stay-picture {
    width: 120px;
    height: 90px;
    border-radius: 10px;
    object-fit: cover;
    cursor: zoom-in;
}
/* On a narrow phone the address needs the width more than the picture does. */
@media (max-width: 400px) {
    .stay-picture { width: 96px; height: 72px; }
}

/* Places of interest: the search-and-pick list. A candidate is a button
   rather than a row with a button in it — the whole card is the target,
   which matters more here than almost anywhere else in the app, since
   picking the *right* one out of several similar ones rewards not having to
   aim. */
.place-candidates {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 280px;
    overflow-y: auto;
}

.place-candidate button {
    width: 100%;
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    column-gap: 8px;
    text-align: start;
    padding: 8px 10px;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: inherit;
    font: inherit;
    cursor: pointer;
}
.place-candidate button:hover,
.place-candidate button:focus-visible {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 182, 193, 0.45);
}

.place-candidate-name { color: #fff; font-weight: 600; font-size: 0.9rem; }
.place-candidate-address { flex-basis: 100%; color: #8479a0; font-size: 0.78rem; }

.flight-leg {
    font-size: var(--fs-label);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #8479a0;
}

.flight-number {
    font-size: 1.05rem;
    font-weight: 600;
    color: #64ffda;
    text-decoration: none;
    letter-spacing: 0.02em;
}
.flight-number:hover { text-decoration: underline; }

.flight-airline { font-size: 0.85rem; color: #a8b2d1; }

/* Who is on it, changeable in place. Sits between the leg and the number, so
   the line reads "Out — Maria — LO156". */
.flight-who {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: #d8d2e8;
    border-radius: 6px;
    padding: 2px 4px;
    font-family: inherit;
    font-size: 0.78rem;
    outline: none;
    cursor: pointer;
}
.flight-who:hover { border-color: rgba(255, 255, 255, 0.35); color: #fff; }
.flight-who:focus { border-color: #8a2be2; }
.flight-who option { background: #1a1030; color: #d8d2e8; }

/* The times are what you scan this block for, so they read like data: aligned
   digits, and brighter than the label above them. */
.flight-when {
    margin: 0;
    font-size: 0.95rem;
    color: #d8d2e8;
    font-variant-numeric: tabular-nums;
}

.flight-when del { color: #8479a0; text-decoration-color: #ffb86b; }

.flight-problem { text-align: left; margin: 2px 0 0; }
.flight-status { text-align: left; margin: 2px 0 0; font-weight: 600; }
.flight-actions { display: flex; gap: 6px; margin-top: 4px; }

.flight-form { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; align-items: center; }
.flight-form input, .flight-form select {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 7px 10px;
    font-family: inherit;
    font-size: 0.88rem;
    outline: none;
}
.flight-form input:focus, .flight-form select:focus { border-color: #8a2be2; }
#flightNumber { flex: 0 1 110px; text-transform: uppercase; }
.meeting-flights .hint { text-align: left; }

/* ------------------------------------------------ the full-size picture view */

.lightbox {
    border: none;
    padding: 0;
    background: transparent;
    max-width: 100vw;
    max-height: 100dvh;
    width: 100vw;
    height: 100dvh;
    overflow: auto;
    /* Centres the picture, and lets the caption and button sit under it. */
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    color: #d8d2e8;
}

/* `display` has to switch with the open state, or the dialog is laid out even
   while closed — and a flex dialog ignores the browser's own hiding. */
.lightbox[open] { display: flex; }

.lightbox::backdrop {
    background: rgba(8, 3, 18, 0.94);
    backdrop-filter: blur(6px);
}

/* The picture springs back when a swipe was not meant. No `touch-action` here
   on purpose: setting it would stop the browser interfering with a drag, and
   would take pinch-to-zoom with it. */
.lightbox img { transition: transform 0.18s ease; }
.lightbox img.swiping { transition: none; }

@media (prefers-reduced-motion: reduce) {
    .lightbox img { transition: none; }
}

.lightbox img {
    max-width: 94vw;
    max-height: 78dvh;
    object-fit: contain;
    border-radius: 10px;
    cursor: zoom-in;
}

/* A censored picture, opened. Blurred until the button is pressed, and blurred
   again the moment you move off it — see `censor` in lib/pictures.js. The
   zoom cursor goes with it: tapping the picture does nothing until it has been
   revealed, so a tap meant to enlarge can't be mistaken for one meant to
   uncover. */
.lightbox.censored img {
    filter: blur(34px);
    cursor: default;
    /* Clipped rather than scaled: a blur this heavy bleeds past the edges, and
       enlarging it would push the unblurred rim of the picture into view. */
    clip-path: inset(0 round 10px);
}

/* A censored picture's caption is hidden with it. Captions default to the
   file's own name, so "hotel-bathroom.jpg" under a blurred picture would say
   most of what the blur was for. It comes back with the picture. */
.lightbox.censored .lightbox-caption { visibility: hidden; }

/* In the flow, directly under the picture, rather than floating at the middle
   of the screen: the dialog is a column and the picture is not centred in the
   viewport, so a fixed button landed over the middle of the image — hiding
   part of what you had just asked to see. */
.lightbox-reveal {
    margin: 4px 0 0;
    padding: 12px 22px;
    border: 1px solid rgba(255, 255, 255, 0.35);
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.62);
    backdrop-filter: blur(4px);
    color: #fff;
    font: inherit;
    font-size: 0.95rem;
    cursor: pointer;
}
.lightbox-reveal:hover,
.lightbox-reveal:focus-visible {
    border-color: #ffb6c1;
    background: rgba(0, 0, 0, 0.78);
}

/* Tapping again drops the fit and shows the picture at its own size; the
   dialog scrolls, which is what looking closely at a photo actually needs. */
.lightbox.zoomed { justify-content: flex-start; align-items: flex-start; }
.lightbox.zoomed img { max-width: none; max-height: none; cursor: zoom-out; border-radius: 0; }

.lightbox video {
    max-width: 94vw;
    max-height: 78dvh;
    border-radius: 10px;
}

.lightbox-caption {
    margin: 0;
    font-size: 0.9rem;
    color: #a8b2d1;
    text-align: center;
    max-width: 90vw;
    unicode-bidi: plaintext;
}
.lightbox.zoomed .lightbox-caption, .lightbox.zoomed #lightboxClose { display: none; }
.lightbox-caption[hidden] { display: none; }

/* A forecast beside the booked time, quieter than it: the booked time is
   still the one that is true until the aeroplane says otherwise. */
.flight-expected { color: #a8b2d1; font-size: var(--fs-small); }

/* Words under a picture: read where the caption is, written in the same
   place, at the width of the caption rather than the screen. */
.lightbox-caption { white-space: pre-line; line-height: 1.45; max-width: min(90vw, 640px); }
.lightbox-describe { margin: 0; }
.lightbox-describe-form {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: min(90vw, 640px);
}
.lightbox-describe-form[hidden] { display: none; }
.lightbox-describe-form textarea { width: 100%; box-sizing: border-box; height: auto; resize: vertical; }
.lightbox-describe-buttons { display: flex; gap: 8px; justify-content: center; }
.lightbox.zoomed .lightbox-describe, .lightbox.zoomed .lightbox-describe-form { display: none; }
/* A censored picture's words are as hidden as the picture until it is lifted. */
.lightbox.censored .lightbox-describe { visibility: hidden; }

.link-form { display: flex; flex-wrap: wrap; gap: 6px; }
.link-form input { flex: 1 1 160px; font-size: 0.88rem; padding: 7px 10px; }

/*
 * The finished ones, and the button that reveals them.
 *
 * The button had no top margin, so it sat flush against the last todo and
 * read as part of the list rather than as something below it. The list itself
 * has `margin: 18px 0 0`, so this matches the gap above the list — the block
 * ends up evenly spaced top and bottom instead of hanging off the last row.
 */
#toggleDoneBtn { margin-top: 16px; }

.done-list { margin-top: 10px; }

.locked-panel { text-align: center; }
.locked-title { font-size: 1.1rem; color: #fff; margin: 0 0 8px; }
.locked-panel p:last-child { color: #8479a0; font-size: 0.9rem; margin: 0; }

@media (max-width: 600px) {
    .list-panel { padding: 16px 12px; }
    .list-panel-actions { margin-left: 0; width: 100%; }
    .attachment img { max-width: 100%; }
}

/* ===============================================================
   The single-page shell.

   One document now: a header that never moves, and views swapped
   underneath it. `body` scrolls, since the views are documents.
   =============================================================== */

body {
    display: block;
    height: auto;
    min-height: 100dvh;
    overflow-y: auto;
}

.app {
    position: relative;
    z-index: 10;
    width: min(94%, var(--app-width));
    margin: 0 auto;
    /* The bar is fixed and floats over the page, so the page has to end above
       it: 58px of tab, the safe area under it, and a gap so the last line of a
       view is not touching it. Over 760px the bar is at the top instead and
       this becomes the other way round — see the media query below. */
    padding: 18px 0 calc(78px + env(safe-area-inset-bottom, 0px));
}

/*
 * The header is a tight cluster on purpose — eight pixels from the caption to
 * the number, six from the number to the place. What it had none of was a gap
 * underneath: the view began on the same pixel the header ended, so an opened
 * note sat four pixels under "Warsaw, Poland #8" and read as part of it.
 *
 * Here rather than on the jar, because every page has this edge and every page
 * had it wrong — the jar is only where it shows most, being the page whose
 * first element is a card with a border.
 */
.site-header { text-align: center; margin-bottom: 22px; }

.header-top {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

/* The toggle used to float in the card's corner; in a flex bar it just sits. */
.site-header .toggle-timer-btn { position: static; }
.site-header .lang-select { position: static; }

/* Quieter and closer to the number it introduces: the number says the thing,
   and this used to be set at nearly the size of the answer itself. */
#mainTitle { margin: 2px 0 8px; font-size: var(--fs-small); letter-spacing: 0.1em;
             text-transform: uppercase; color: #8479a0; }

/* The countdown: full size on the jar, one line everywhere else. */
#timer.compact {
    display: block;
    font-size: 1.15rem;
    letter-spacing: 1px;
    font-variant-numeric: tabular-nums;
    margin: 0;
    min-height: 0;
}
/* Plain text, not the .num/.unit spans the ticking digits use, so it needs its
   own size — otherwise it inherits #timer's own (unset) size and reads as a
   caption where a 4rem countdown sat a second ago. */
#timer.finished {
    color: #ff6b6b;
    text-shadow: 0 0 10px rgba(255, 107, 107, 0.8);
    font-size: 4rem;
    font-weight: bold;
    letter-spacing: 2px;
}
/* Compact stays compact even once it's finished — this is the one-line
   countdown shown on every page but the jar, and "the time has come" is not
   worth reclaiming that space from the page it's a caption on. */
#timer.compact.finished { font-size: 1.15rem; font-weight: normal; letter-spacing: 1px; }

/* On a phone. This used to sit in the phone block near the top of the file,
   where the rule above — the same selector, later — quietly undid it, so a
   phone got the full 4rem: "HAS COME" alone is 367px at that size, wider than
   the column, and the line broke into three. At 2.2rem it is two lines in
   English and one in Ukrainian and Hebrew, at 320px as well as 375, and
   `balance` keeps the two English lines even rather than a long one and a
   stray word. */
@media (max-width: 600px) {
    #timer.finished { font-size: 2.2rem; line-height: 1.15; text-wrap: balance; }
}

.meeting-location {
    display: inline-block;
    margin: 6px 0 0;
    color: #ffb6c1;
    font-size: 0.95rem;
    letter-spacing: 0.04em;
    text-decoration: none;
    border-bottom: 1px dashed rgba(255, 182, 193, 0.45);
}
a.meeting-location:hover { color: #fff; border-bottom-color: #fff; }

/* Views. `hidden` is what the router toggles; the rest is layout. */
.view[hidden] { display: none; }
.view { animation: viewIn 0.18s ease-out; }
.view.page { text-align: left; }
/* Rows of words with their buttons at the far end — a list, the kit, a
   contact, a setting, the log. At the full width of a desktop the eye loses
   the line between a name and its controls, so these keep the measure the
   whole app used to have. The pages that are laid out rather than read — the
   board, the calendar, the map, the album — take all of it. */
.view.page.reading { max-width: var(--read-width); margin-inline: auto; }

@keyframes viewIn {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .view { animation: none; }
}

/* The jar keeps its centred card feel without being a card. */
#viewJar { text-align: center; }

.note-occasion {
    margin: 22px 0 -8px;
    color: #ffb6c1;
    font-size: 0.9rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

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

.meeting-form {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 12px;
    margin-top: 20px;
}
.meeting-form label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: var(--fs-label);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #8479a0;
}
.meeting-form label.wide { grid-column: 1 / -1; }
.meeting-form input, .meeting-form textarea {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 9px 12px;
    font-family: inherit;
    font-size: 0.95rem;
    outline: none;
}
.meeting-form input:focus, .meeting-form textarea:focus { border-color: #8a2be2; }
.meeting-form-actions { grid-column: 1 / -1; display: flex; gap: 8px; align-items: center; }

.meetings { list-style: none; margin: 24px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }

.meeting {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-left: 3px solid #8a2be2;
    border-radius: 10px;
    padding: 14px 16px;
}
.meeting.past { opacity: 0.6; border-left-color: #5a5a70; }
.meeting.now { border-left-color: #64ffda; }

.meeting-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.meeting-head strong { color: #fff; font-size: 1.02rem; }
.meeting-phase {
    font-size: var(--fs-label);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #8479a0;
    margin-left: auto;
}
.meeting-when { margin: 6px 0 0; font-size: 0.88rem; color: #a8b2d1; }
.meeting-description { margin: 8px 0 0; font-size: 0.9rem; color: #b3a8cd; }
.meeting-actions { display: flex; gap: 6px; margin-top: 12px; }

/* ------------------------------------------------------- the album */

.album-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    /* Every row as tall as the tallest card anywhere, so one long date or
       title makes the whole album a line taller rather than one row of it. */
    grid-auto-rows: 1fr;
    gap: 14px;
    margin-top: 24px;
}

.album-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    padding: 10px;
    color: #d8d2e8;
    box-shadow: none;
    font-weight: normal;
    text-align: center;
}
.album-card:hover { background: rgba(255, 255, 255, 0.1); transform: translateY(-2px); }
.album-card.upcoming { border-color: rgba(100, 255, 218, 0.4); }
.album-card strong { color: #fff; font-size: 0.95rem; }
/* The date and length are what you actually scan an album by, so they read
   as information rather than as a footnote. */
.album-when {
    font-size: 0.84rem;
    color: #ffb6c1;
    font-weight: 600;
}
/* Each half stays whole — see renderCard in views/meetings.js. */
.album-when > span { white-space: nowrap; }

.album-cover {
    aspect-ratio: 4 / 3;
    border-radius: 8px;
    overflow: hidden;
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
}
.album-cover img { width: 100%; height: 100%; object-fit: cover; }
.album-cover-empty { font-size: 2rem; opacity: 0.5; }

.album-detail { text-align: left; }
.album-detail h2 { margin: 12px 0 4px; color: #fff; font-size: 1.2rem; }
.album-detail > p { margin: 0 0 10px; }

/* Same treatment inside a meeting: bigger than the body text, and coloured. */
#albumWhen {
    font-size: 1rem;
    color: #ffb6c1;
    font-weight: 600;
    letter-spacing: 0.02em;
    margin: 0 0 12px;
}

#albumDescription {
    font-size: 1rem;
    line-height: 1.6;
    color: #e8e2f5;
    font-style: italic;
    border-left: 2px solid rgba(255, 182, 193, 0.5);
    padding-left: 12px;
    unicode-bidi: plaintext;
}

/* A grid of equal frames. Pictures are squares of the same size whatever shape
   they were taken in; a written excerpt is a different kind of thing, so it
   spans the row rather than pretending to be a tile. */
.album-media {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 10px;
    margin: 18px 0;
    align-items: start;
}

/* A tile is a `figure` on the card panel and a `button` in the album, and a
   `figure` carries the browser's own `margin: 1em 40px`. Inside a 120px grid
   cell that leaves almost nothing, which is exactly how the card's pictures
   came to be a quarter the size of the same pictures everywhere else. */
.media-tile { margin: 0; }

.album-tile,
.pic-tile {
    position: relative;
    aspect-ratio: 1;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 10px;
    overflow: hidden;
    background: rgba(0, 0, 0, 0.3);
    box-shadow: none;
    cursor: zoom-in;
    display: block;
}

.album-tile:hover, .pic-tile:hover { transform: none; border-color: rgba(255, 182, 193, 0.5); }
.album-tile:focus-visible, .pic-tile:focus-visible { outline: 2px solid #8a2be2; outline-offset: 2px; }

.album-tile img, .pic-tile img, .album-tile video {
    width: 100%;
    height: 100%;
    /* Fills the frame and crops, so a portrait and a landscape sit side by
       side without one of them letterboxed. */
    object-fit: cover;
    display: block;
    opacity: 1;
    transition: opacity 0.25s ease;
}

.album-tile.loading img, .pic-tile.loading img, .album-tile.loading video,
.album-tile.skeleton img, .pic-tile.skeleton img { opacity: 0; }
.album-tile.skeleton, .pic-tile.skeleton { cursor: default; }
.album-tile.broken, .pic-tile.broken { border-style: dashed; }

/* A video tile is a muted, controls-less <video> standing in for its own
   first frame, with just enough overlay to say it plays sound and moves. */
.tile-play {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
.tile-play::before {
    content: "▶";
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding-left: 3px;
    border-radius: 50%;
    background: rgba(8, 3, 18, 0.55);
    color: #fff;
    font-size: 0.85rem;
}

/* Centred in the frame the picture is about to fill. */
.tile-spinner {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 20px;
    height: 20px;
    margin: -10px 0 0 -10px;
    border: 2px solid rgba(255, 182, 193, 0.25);
    border-top-color: #ff6b81;
    border-radius: 50%;
    animation: spinSync 0.8s linear infinite;
}

.album-tile:not(.loading):not(.skeleton) .tile-spinner,
.pic-tile:not(.loading):not(.skeleton) .tile-spinner { display: none; }

@media (prefers-reduced-motion: reduce) {
    .tile-spinner { animation-duration: 2.4s; }
}

/* ---------------------------------------------------------------
   A censored picture.

   The blur is drawn in the browser over the real bytes — the picture
   is delivered either way, and a network tab shows it. That is the
   right size of defence for what this is for: somebody glancing over
   a shoulder, or the grid being scrolled past on a tilted phone. It
   is not a lock and does not pretend to be one.

   Heavy on purpose, and scaled up underneath so no edge of the real
   picture shows around the blur. A tile is never revealed — only the
   lightbox has an Uncensor button.
   --------------------------------------------------------------- */

.album-tile.censored img {
    filter: blur(18px);
    transform: scale(1.2);
}
.tile-censored {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 1.6rem;
    text-shadow: 0 1px 6px rgba(0, 0, 0, 0.9);
    pointer-events: none;
}

.tile-flag {
    position: absolute;
    top: 5px;
    inset-inline-end: 5px;
    color: #ffb6c1;
    font-size: 0.9rem;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
}

.tile-reactions {
    position: absolute;
    bottom: 4px;
    inset-inline-start: 5px;
    font-size: 0.8rem;
    background: rgba(8, 3, 18, 0.7);
    border-radius: 999px;
    padding: 1px 7px;
}

.album-item.text {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: rgba(0, 0, 0, 0.25);
    border-radius: 10px;
    padding: 12px 14px;
}
.album-item.text.promoted { outline: 1px solid rgba(255, 182, 193, 0.55); outline-offset: 2px; }
.album-item blockquote {
    margin: 0;
    font-size: 0.95rem;
    line-height: 1.55;
    color: #e8e2f5;
    unicode-bidi: plaintext;
}
.album-caption { margin: 0; font-size: 0.85rem; color: #a8b2d1; }
.album-by { font-size: var(--fs-label); color: #8479a0; }
.album-item-actions { display: flex; flex-direction: column; gap: 8px; }
.album-item-buttons { display: flex; flex-wrap: wrap; gap: 6px; }
.album-promote.on { color: #ffb6c1; border-color: rgba(255, 182, 193, 0.5); }

/* The picture's own controls, shown under it once it is open — there is no
   room for them on a thumbnail, and nowhere better to put them. */
.lightbox-extras {
    display: flex;
    flex-direction: column;
    gap: 10px;
    align-items: center;
    max-width: 90vw;
}
.lightbox.zoomed .lightbox-extras { display: none; }

/* Big enough to hit with a thumb, and out of the way of the picture. */
.lightbox-arrow {
    position: fixed;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    background: rgba(8, 3, 18, 0.72);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: #fff;
    font-size: 1.8rem;
    line-height: 1;
    padding: 10px 16px;
    border-radius: 999px;
    box-shadow: none;
}

.lightbox-arrow:hover {
    background: rgba(8, 3, 18, 0.92);
    border-color: #ffb6c1;
    transform: translateY(-50%);
}

.lightbox-arrow.prev { left: 10px; }
.lightbox-arrow.next { right: 10px; }

/* At full size the picture is what you are scrolling; arrows over it would sit
   in the way of the thing you zoomed in to look at. */
.lightbox.zoomed .lightbox-arrow { display: none; }

.album-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 10px; }
.album-add textarea {
    flex: 1 1 220px;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 9px 12px;
    font-family: inherit;
    font-size: 0.95rem;
    outline: none;
    resize: vertical;
}
.album-add textarea:focus { border-color: #8a2be2; }

@media (max-width: 600px) {
    /* Tighter at the top, because a phone's screen is mostly length. The
       bottom is the same reserve as always and has to keep the safe area in
       it: this rule comes after the one above and wins, and for a while it won
       with a flat 60px — which on a phone with a home indicator put the last
       line of every page under the indicator and the bar both. */
    .app { padding: 12px 0 calc(74px + env(safe-area-inset-bottom, 0px)); }
    #timer.compact { font-size: 1rem; }
    .album-item img { max-width: 100%; }
}

/* The bar moved to the top, so the room reserved for it moves with it: 50px of
   tab, one of border, then a gap before the header. The bottom goes back to
   being a plain end-of-page margin — there is nothing floating over it now,
   and no home indicator on a laptop. */
@media (min-width: 760px) {
    .app { padding: calc(51px + 22px) 0 40px; }
}

/* ---------------------------------------------------------------
   The admin tools, which are a set of tabs rather than one page.
   --------------------------------------------------------------- */

.admin-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin: 20px 0 4px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}

.admin-tabs a {
    color: #8479a0;
    text-decoration: none;
    font-size: 0.9rem;
    padding: 8px 16px;
    border-radius: 8px 8px 0 0;
    /* Reserves the active border on every tab, so selecting one does not
       shift the row by a pixel. */
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.admin-tabs a:hover { color: #d8d2e8; background: rgba(255, 255, 255, 0.05); }

.admin-tabs a.active {
    color: #ffb6c1;
    border-bottom-color: #ffb6c1;
    background: rgba(255, 255, 255, 0.06);
}

.admin-tabs a:focus-visible { outline: 2px solid #8a2be2; outline-offset: -2px; }

/* ---------------------------------------------------------------
   Reactions. Two people, so a name is worth more than a count.
   --------------------------------------------------------------- */

.reactions-holder { margin-top: 14px; }

.reactions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
    justify-content: center;
    position: relative;
}

.album-item .reactions { justify-content: flex-start; }

button.reaction {
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.16);
    color: #d8d2e8;
    box-shadow: none;
    font-weight: normal;
    font-size: 0.82rem;
    padding: 4px 10px;
    border-radius: 999px;
    line-height: 1.4;
}

button.reaction:hover { background: rgba(255, 255, 255, 0.16); transform: none; }

/* Yours, so you can see at a glance which ones pressing again would remove. */
button.reaction.mine {
    background: rgba(255, 107, 129, 0.22);
    border-color: rgba(255, 107, 129, 0.55);
    color: #fff;
}

button.reaction.add { padding: 4px 9px; color: #8479a0; }

/* All eight on one line, on the narrowest phone too. That only works if the
   emoji themselves are allowed to shrink, so the size is tied to the viewport
   rather than fixed — and `nowrap` makes a second row impossible rather than
   merely unlikely. */
.reaction-palette {
    display: flex;
    flex-wrap: nowrap;
    justify-content: center;
    gap: clamp(1px, 0.6vw, 4px);
    max-width: 96vw;
    background: rgba(20, 12, 34, 0.96);
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 999px;
    padding: 5px 7px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
}

.reaction-palette button.reaction {
    padding: 2px clamp(3px, 1.1vw, 7px);
    font-size: clamp(0.85rem, 3.4vw, 1.05rem);
    flex: 0 0 auto;
}

/* A log line that goes somewhere should look like it does. */
a.event-summary { color: #d8d2e8; text-decoration: none; display: block; }
a.event-link { border-bottom: 1px dashed rgba(255, 182, 193, 0.4); }
a.event-link:hover { color: #fff; border-bottom-color: #ffb6c1; }

/* --------------------------------------------------------------
   Meetings: the place under the name, and what has not happened yet.
   -------------------------------------------------------------- */

/* Only ever rendered when a meeting has a name of its own, so it never just
   repeats the title. */
.album-where {
    font-size: 0.78rem;
    color: #a8b2d1;
    letter-spacing: 0.02em;
}

.album-where-line {
    margin: 0 0 6px;
    font-size: 0.95rem;
    color: #a8b2d1;
}

.album-card.upcoming { border-color: rgba(100, 255, 218, 0.45); }
.album-card.now { border-color: #64ffda; }

/* Sat on the cover rather than under the title: it is a state, not a label. */
/* ---------------------------------------------------------------
   Hold to peek. One panel reached by a hover, a focus or a long press —
   see lib/peek.js, which owns when each of those counts.
   --------------------------------------------------------------- */

/* The panel lies over the whole card, so it needs something to lie over. */
.album-card { position: relative; }

.peek-panel {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow-y: auto;
    padding: 14px;
    border-radius: 12px;
    background: rgba(10, 4, 22, 0.94);
    color: #e8e2f5;
    font-size: 0.86rem;
    line-height: 1.5;
    text-align: center;
    /* A description is a paragraph somebody wrote; it keeps its shape. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    animation: peekIn 0.14s ease-out;
}

@keyframes peekIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .peek-panel { animation: none; }
}

/* The card lifts on hover, which under a panel reads as the panel wobbling. */
.album-card.peeking:hover { transform: none; }

/* Says there is something to hold for. Bottom-end, because the phase flag has
   the top-start corner and two marks in one corner is neither of them. */
.peek-mark {
    position: absolute;
    bottom: 6px;
    inset-inline-end: 6px;
    font-size: var(--fs-label);
    line-height: 1;
    padding: 3px 5px;
    border-radius: 999px;
    background: rgba(8, 3, 18, 0.82);
    opacity: 0.85;
}

/* A long press on a phone otherwise offers to select the title or save the
   cover picture, on top of the panel that press was asking for. */
.album-card { -webkit-touch-callout: none; }

.album-flag {
    position: absolute;
    top: 6px;
    inset-inline-start: 6px;
    background: rgba(8, 3, 18, 0.82);
    color: #64ffda;
    border: 1px solid rgba(100, 255, 218, 0.5);
    border-radius: 999px;
    padding: 2px 9px;
    font-size: var(--fs-label);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.album-cover { position: relative; }

.album-upcoming {
    display: inline-block;
    margin: 0 0 8px;
    color: #64ffda;
    border: 1px solid rgba(100, 255, 218, 0.5);
    border-radius: 999px;
    padding: 2px 12px;
    font-size: var(--fs-label);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.meeting-form-title {
    grid-column: 1 / -1;
    margin: 0;
    font-size: 1.05rem;
    color: #fff;
    font-weight: 600;
}

.meeting-form { margin-bottom: 8px; }
.meeting-form .hint { grid-column: 1 / -1; text-align: start; }

.meeting-detail-actions { display: flex; gap: 8px; margin: 12px 0 4px; }

/* ============================================================== the calendar

   Four views of the same days — a schedule, a day, three days and the
   month grid — with one row of buttons to choose between them. Which
   one you get is views/calendar.js's business; this file draws them.

   The month grid is six rows always. Five-row months exist and a grid
   that changes height as you page through the year makes everything
   below it jump — including the day you were reaching for.

   Sizes here are the scale's tokens rather than hand-picked decimals.
   This page was written before the scale existed and had four
   different sizes of grey under 13px on it, which is the page Maria
   was talking about when she said the text was too small. */

/* --------------------------------------------------- the four views */

.cal-views {
    display: flex;
    justify-content: center;
    gap: 2px;
    width: fit-content;
    max-width: 100%;
    margin: 0 auto 14px;
    padding: 3px;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.09);
    border-radius: 999px;
}

.cal-view-btn {
    min-height: 38px;
    padding: 6px 14px;
    background: none;
    border: none;
    border-radius: 999px;
    box-shadow: none;
    color: #b7aed0;
    font-family: inherit;
    font-size: var(--fs-small);
    font-weight: normal;
    white-space: nowrap;
}
.cal-view-btn:hover,
.cal-view-btn:focus-visible {
    background: rgba(255, 255, 255, 0.07);
    color: #fff;
    transform: none;
}
/* Which one you are on, in the colour the rest of the page uses for "here". */
.cal-view-btn.chosen { background: rgba(255, 107, 129, 0.22); color: #ffd9df; }

.cal-head {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    margin-bottom: 12px;
    flex-wrap: wrap;
}
.cal-month {
    margin: 0;
    font-size: var(--fs-lead);
    color: #fff;
    /* Wide enough that the arrows do not move between "May" and "September",
       and between a month's name and a three-day span. */
    min-width: 9.5em;
    text-align: center;
}

.cal-weekdays, .cal-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 4px;
}
.cal-weekdays {
    margin-bottom: 4px;
    font-size: var(--fs-label);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #9f96bd;
    text-align: center;
}

.cal-cell {
    /* A button so the whole square is one tap target and the keyboard can
       reach it — the day panel is the only way to read a busy day. */
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    min-height: 82px;
    padding: 5px 4px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 8px;
    box-shadow: none;
    font-weight: normal;
    text-align: start;
    overflow: hidden;
}
.cal-cell:hover { background: rgba(255, 255, 255, 0.09); }
.cal-cell.outside { opacity: 0.4; }
.cal-cell.today { border-color: rgba(255, 107, 129, 0.7); }
.cal-cell.chosen { background: rgba(255, 107, 129, 0.16); border-color: #ff6b81; }

.cal-date { font-size: var(--fs-small); color: #d8d2e8; }
.cal-cell.today .cal-date { color: #ff6b81; font-weight: 600; }

.cal-chip {
    font-size: var(--fs-label);
    line-height: 1.4;
    padding: 2px 5px;
    border-radius: 5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    background: rgba(255, 107, 129, 0.28);
    color: #ffd9df;
}
/* The kinds are told apart by colour, not by a word: there is no room for a
   word in a square this size. The panel and the schedule name them.

   Said once for the chip on a month square and the block on an hour grid,
   which are the same fact at two sizes. */
.cal-chip.kind-meeting,
.cal-block.kind-meeting { background: rgba(100, 255, 218, 0.22); color: #b8fff0; }
.cal-chip.kind-flight,
.cal-block.kind-flight { background: rgba(130, 170, 255, 0.24); color: #d6e2ff; }
/* A near neighbour of the flight's, because they answer the same question and
   a day with both on it should read as one journey rather than two systems. */
.cal-chip.kind-train,
.cal-block.kind-train { background: rgba(130, 220, 255, 0.22); color: #cdefff; }
.cal-chip.kind-card,
.cal-block.kind-card { background: rgba(255, 184, 107, 0.22); color: #ffd7ab; }
.cal-chip.kind-habit,
.cal-block.kind-habit { background: rgba(180, 140, 255, 0.22); color: #ded0ff; }

/* And the two that are not appointments at all. Warmer and brighter than
   anything else on the page on purpose: a birthday is the one thing here you
   would be sorry to scroll past, and it is competing with a grid of grey. */
.cal-chip.occasion-birthday,
.cal-block.occasion-birthday {
    background: linear-gradient(135deg, rgba(255, 201, 77, 0.34), rgba(255, 143, 177, 0.26));
    color: #ffeec2;
}
.cal-chip.occasion-anniversary,
.cal-block.occasion-anniversary {
    background: linear-gradient(135deg, rgba(255, 143, 177, 0.34), rgba(186, 140, 255, 0.28));
    color: #ffdcea;
}

.cal-chip.skipped { text-decoration: line-through; opacity: 0.55; }

/* Done is struck through rather than removed. A habit that vanished when it
   was ticked would leave a calendar that cannot tell "done" from "never
   asked for" — which is the one distinction the dots on the board are made of. */
.cal-chip.done { text-decoration: line-through; opacity: 0.5; }

.cal-more { font-size: var(--fs-label); color: #9f96bd; }

.cal-day { text-align: start; }
.cal-day-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}
.cal-day-head h3 { margin: 0; font-size: var(--fs-lead); color: #fff; }

.cal-entries { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }

.cal-entry {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px 10px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-inline-start: 3px solid rgba(255, 107, 129, 0.75);
    border-radius: 8px;
}
.cal-entry.kind-meeting { border-inline-start-color: rgba(100, 255, 218, 0.75); }
.cal-entry.kind-flight { border-inline-start-color: rgba(130, 170, 255, 0.75); }
.cal-entry.kind-train { border-inline-start-color: rgba(130, 220, 255, 0.75); }
.cal-entry.kind-card { border-inline-start-color: rgba(255, 184, 107, 0.75); }
.cal-entry.kind-habit { border-inline-start-color: rgba(180, 140, 255, 0.75); }

/* An occasion's line: the colour down the edge, and a wash of it behind the
   row. Flat rather than a gradient, because the language switch sets the
   direction on an element rather than with a `dir` attribute — there is no
   selector here that could turn a left-to-right gradient round for Hebrew. */
.cal-entry.occasion-birthday {
    border-inline-start-color: #ffc94d;
    background: rgba(255, 201, 77, 0.11);
}
.cal-entry.occasion-anniversary {
    border-inline-start-color: #ff8fb1;
    background: rgba(255, 143, 177, 0.11);
}
.cal-entry.occasion-birthday .cal-tags { color: #ffd88a; }
.cal-entry.occasion-anniversary .cal-tags { color: #ffb3cd; }

/* The cake, where the clock would be. Big enough to be the thing you see. */
.cal-when-glyph { font-size: 1.4rem; line-height: 1.15; text-align: center; }
.cal-entry.skipped { opacity: 0.6; }
.cal-entry.skipped .cal-title { text-decoration: line-through; }
.cal-entry.done { opacity: 0.55; }
.cal-entry.done .cal-title { text-decoration: line-through; }

.cal-when {
    flex: none;
    min-width: 3.8em;
    font-size: var(--fs-small);
    color: #d8d2e8;
    font-variant-numeric: tabular-nums;
}
.cal-entry-body { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cal-title { color: #fff; font-size: var(--fs-body); line-height: 1.35; }
.cal-detail { color: #b6bfdb; font-size: var(--fs-small); }
.cal-tags { color: #9f96bd; font-size: var(--fs-label); }
.cal-entry-actions { flex: none; display: flex; gap: 6px; align-items: center; }

.cal-form { text-align: start; display: grid; gap: 10px; }
.cal-form h3 { margin: 0; font-size: var(--fs-lead); color: #fff; }
.cal-form label { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-small); color: #b6bfdb; }
.cal-form .private-toggle { flex-direction: row; align-items: center; gap: 8px; }
.cal-form-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.cal-form .hint { text-align: start; }


/* ------------------------------------------------------- the schedule */

/* Everything from here forward, one heading per day, empty days left out. The
   gap between two dates is itself the answer to "when is the next thing", and
   sixty headings saying nothing under fifty-eight of them would bury it. */

.cal-agenda { display: grid; gap: 20px; text-align: start; }
.cal-agenda-day { display: grid; gap: 8px; }

.cal-agenda-head {
    margin: 0;
    padding-bottom: 6px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.09);
    color: #b7aed0;
    font-size: var(--fs-small);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.cal-agenda-day.is-today .cal-agenda-head {
    color: #ff9fb0;
    border-bottom-color: rgba(255, 107, 129, 0.45);
}

/* ------------------------------------------ one day, or three of them */

/*
 * An hour grid: the hours down the side, a column per day, and every entry
 * drawn where it actually sits. Two things at the same time sit beside each
 * other rather than on top of each other — seeing that they collide is most of
 * the reason to draw a day this way at all.
 *
 * The heights are in one place: an hour is --cal-hour tall and a day is
 * twenty-four of them. views/calendar.js positions everything inside a column
 * as a percentage, so it never needs to know what that number is.
 */
.cal-timeview {
    --cal-hour: 46px;
    --cal-gutter: 52px;
    --cal-columns: 1;
    text-align: start;
}

.cal-time-head {
    display: grid;
    grid-template-columns: repeat(var(--cal-columns), 1fr);
    gap: 4px;
    padding-inline-start: var(--cal-gutter);
    margin-bottom: 6px;
}

.cal-time-day {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 6px;
    min-height: 40px;
    padding: 4px 6px;
    background: none;
    border: none;
    border-radius: 8px;
    box-shadow: none;
    color: #b7aed0;
    font-family: inherit;
    font-weight: normal;
}
.cal-time-day:hover { background: rgba(255, 255, 255, 0.06); color: #fff; transform: none; }
.cal-time-weekday { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: 0.04em; }
/* Seven of them share the width a day view gives to one, so the header stacks
   rather than sitting in a row it cannot fit in. */
.cal-timeview.week .cal-time-day { flex-direction: column; align-items: center; gap: 0; }
.cal-timeview.week .cal-time-date { font-size: var(--fs-body); }
.cal-time-date { font-size: var(--fs-lead); color: #fff; }
.cal-time-day.today .cal-time-date,
.cal-time-day.today .cal-time-weekday { color: #ff6b81; }

/* Above the hours, because a birthday does not happen at nine o'clock — it
   happens on a day, and hanging it on the grid would claim an hour of it. */
.cal-time-allday {
    display: grid;
    grid-template-columns: repeat(var(--cal-columns), 1fr);
    gap: 4px;
    padding: 6px 6px 6px var(--cal-gutter);
    margin-bottom: 6px;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 10px;
}
.cal-allday-cell { display: grid; gap: 3px; align-content: start; }

.cal-time-scroll {
    display: grid;
    grid-template-columns: var(--cal-gutter) 1fr;
    max-height: 62vh;
    overflow-y: auto;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 10px;
}

.cal-time-hours { position: relative; height: calc(var(--cal-hour) * 24); }
.cal-hour {
    position: absolute;
    inset-inline-end: 7px;
    transform: translateY(-50%);
    color: #9f96bd;
    font-size: var(--fs-label);
    font-variant-numeric: tabular-nums;
}

.cal-time-days {
    position: relative;
    height: calc(var(--cal-hour) * 24);
    display: grid;
    grid-template-columns: repeat(var(--cal-columns), 1fr);
}

/* The hour lines are a repeating gradient rather than 24 elements per column:
   the three-day view would otherwise be seventy-two divs that exist only to be
   one pixel tall. */
.cal-time-col {
    position: relative;
    border-inline-start: 1px solid rgba(255, 255, 255, 0.08);
    background-image: repeating-linear-gradient(
        to bottom,
        rgba(255, 255, 255, 0.07) 0 1px,
        transparent 1px var(--cal-hour));
}
.cal-time-col.today { background-color: rgba(255, 107, 129, 0.05); }

.cal-block {
    position: absolute;
    display: flex;
    flex-direction: column;
    gap: 1px;
    align-items: stretch;
    overflow: hidden;
    /* A hair off the column's own width, so two side by side are two. */
    margin-inline-end: 2px;
    padding: 3px 6px;
    background: rgba(255, 107, 129, 0.28);
    border: none;
    border-inline-start: 3px solid rgba(255, 107, 129, 0.85);
    border-radius: 6px;
    box-shadow: none;
    color: #ffe3e8;
    font-family: inherit;
    font-size: var(--fs-label);
    font-weight: normal;
    line-height: 1.25;
    text-align: start;
}
.cal-block:hover { filter: brightness(1.18); transform: none; }
.cal-block.short {
    flex-direction: row;
    align-items: baseline;
    gap: 6px;
    padding-block: 2px;
}
.cal-block.short .cal-block-when { flex: none; }
/* And the title takes whatever is left. Without a min-width of zero it refuses
   to shrink below its own text, overflows, and gets cut to nothing by the
   block's overflow — a block saying "09:00" and nothing else. */
.cal-block.short .cal-block-title { flex: 1 1 auto; min-width: 0; }
.cal-block-when { font-variant-numeric: tabular-nums; opacity: 0.85; }
.cal-block-title {
    color: #fff;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.cal-block.skipped, .cal-block.done { opacity: 0.5; }
.cal-block.skipped .cal-block-title,
.cal-block.done .cal-block-title { text-decoration: line-through; }

/* Where the day has got to. Only ever on today's column. */
.cal-now {
    position: absolute;
    inset-inline: 0;
    height: 2px;
    background: #ff6b81;
    box-shadow: 0 0 8px rgba(255, 107, 129, 0.8);
    pointer-events: none;
}
.cal-now::before {
    content: "";
    position: absolute;
    inset-inline-start: -4px;
    top: -3px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #ff6b81;
}

/*
 * On a phone the month grid's squares are too narrow for words, so the chips
 * become dots — a picture of how busy a month is, which is what a month grid
 * is for at this width. It is not a way to *read* a month, and for a long
 * while it was the only thing this page offered a phone. The schedule is now
 * what a narrow screen opens on; this is the grid for when you want the shape.
 *
 * The dots are 7px rather than 5. Three of them and a "+2" have to be legible
 * from arm's length, which was the whole complaint.
 */
@media (max-width: 560px) {
    .cal-cell { min-height: 62px; }
    .cal-chip {
        font-size: 0;
        line-height: 0;
        height: 7px;
        border-radius: 999px;
        padding: 0;
    }

    /* A row on a phone is the width of the screen minus two buttons, which
       left "reminds both of us" wrapping into three lines beside a title with
       room to spare. Wrapped instead: the buttons drop to a line of their own
       when the words need the width. */
    .cal-entry { flex-wrap: wrap; }
    .cal-entry-actions { margin-inline-start: auto; }

    /* The dots above are for squares a seventh of the screen wide. The all-day
       strip is a whole column and has room for the words, so the chips in it
       stay chips — three unlabelled bars over an hour grid would be the one
       part of this page nobody could read at all. */
    .cal-allday-cell .cal-chip {
        font-size: var(--fs-label);
        line-height: 1.4;
        height: auto;
        padding: 2px 5px;
        border-radius: 5px;
    }

    /* The hour grid keeps its text — it has a whole column per day, not a
       seventh of the screen — but the hours themselves get closer together, so
       that a day is a scroll rather than a journey. */
    .cal-timeview { --cal-hour: 40px; --cal-gutter: 46px; }
    .cal-time-scroll { max-height: 56vh; }

    /* Except the week, which is a seventh of the screen per day — about 48px.
       A time and a title do not both fit in that, and of the two the title is
       the one that says what the thing is; where the block sits already says
       when it is. The week on a phone is a shape, and the day and the schedule
       are for reading. */
    .cal-timeview.week .cal-block { padding-inline: 3px; border-inline-start-width: 2px; }
    .cal-timeview.week .cal-block-when { display: none; }
    /* The weekday keeps its size — 13px is the floor and the floor is the
       floor — and gives up the letter-spacing instead, which is what makes
       "WED" too wide for a seventh of a phone. */
    .cal-timeview.week .cal-time-weekday { letter-spacing: 0; }

    /* Four buttons across the narrowest phone this has to work on. */
    .cal-view-btn { padding: 6px 10px; }
}

/* ------------------------------------------------ the unfinished features */

.feature-grid { display: grid; gap: 12px; text-align: start; }
.feature-row {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    padding-bottom: 10px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.feature-row:last-child { border-bottom: none; padding-bottom: 0; }
.feature-what { flex: 1; min-width: 12em; display: flex; flex-direction: column; }
.feature-what small { color: #8479a0; font-size: var(--fs-label); }
.feature-switch {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.82rem;
    color: #d8d2e8;
}

/* ================================================================ the boards

   Columns scroll sideways rather than stacking. A board is a picture of where
   everything is, and a phone that shows one column at a time is a list — which
   is what the lists page already is. */

.board-kind {
    display: block;
    font-size: var(--fs-label);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #8479a0;
}

.board-panel { margin-top: 20px; }

.board-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}
.board-head h2 { margin: 0; font-size: 1.1rem; color: #fff; }
.board-head-actions { display: flex; gap: 8px; flex-wrap: wrap; }

.board-columns {
    display: flex;
    gap: 12px;
    overflow-x: auto;
    padding-bottom: 8px;
    align-items: flex-start;
}

.board-column {
    /* At least the 260 a card needs, and a share of whatever else there is
       up to 420 — past that a column is a lot of empty card. */
    flex: 1 0 260px;
    max-width: 420px;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    padding: 12px;
    text-align: start;
}
/* The three that do something get a thread of colour at the top; a plain
   column deliberately gets none, because it means nothing in particular. */
.board-column.role-planning { border-top: 2px solid rgba(130, 170, 255, 0.6); }
.board-column.role-progress { border-top: 2px solid rgba(255, 184, 107, 0.6); }
.board-column.role-done { border-top: 2px solid rgba(100, 255, 218, 0.6); }

.board-column-head {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}
.board-column-head h3 { margin: 0; font-size: 0.95rem; color: #fff; flex: 1; min-width: 0; }

.column-role {
    font-size: var(--fs-label);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    border-radius: 999px;
    padding: 1px 8px;
    border: 1px solid currentColor;
}
.column-role.role-planning { color: #82aaff; }
.column-role.role-progress { color: #ffb86b; }
.column-role.role-done { color: #64ffda; }

.board-cards { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 8px; }

.board-card {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 8px;
    padding: 8px 10px;
    display: grid;
    gap: 6px;
}
.board-card.done { opacity: 0.6; }
.board-card.done .board-card-title { text-decoration: line-through; }

.board-card-title {
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    font-weight: normal;
    font-size: 0.9rem;
    color: #fff;
    text-align: start;
}
/* No grow. It inherits the site-wide button's 5% scale on hover, which on a
   board's narrow tile was barely there — but on the dashboard the title is as
   wide as the page, and 5% of that pushed its start out over the card's edge. */
.board-card-title:hover { color: #ff6b81; background: none; transform: none; }

.board-card-facts { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.board-card-facts .due {
    font-size: var(--fs-label);
    border-radius: 999px;
    padding: 1px 8px;
    background: rgba(255, 255, 255, 0.08);
    color: #a8b2d1;
}
/* The whole reason a due date is on a closed card: the late ones have to
   catch the eye before they are read. */
.board-card-facts .due.soon { background: rgba(255, 184, 107, 0.2); color: #ffb86b; }
.board-card-facts .due.late { background: rgba(255, 107, 129, 0.24); color: #ff9aab; }
.board-card-facts .who { font-size: var(--fs-label); color: #d8d2e8; }
.board-card-facts .carries { font-size: var(--fs-label); color: #8479a0; }
.board-card-facts .carries.blocked { color: #ff9aab; }

.board-card-move { display: flex; gap: 6px; justify-content: space-between; }

.board-add { display: flex; gap: 6px; }

/* ------------------------------------------------------------ the dashboard */

.board-dashboard { margin-top: 20px; text-align: start; }
.board-mine { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }

/* The same tile a card is on the board, only as wide as the page: one row per
   task, the facts underneath, and an Open button where the arrows would be. */
.mine-row { grid-template-columns: 1fr auto; align-items: start; }
.mine-row .board-card-title { font-size: 0.98rem; }
.mine-row .board-card-facts,
.mine-row .mine-parts { grid-column: 1; }
.mine-row .mine-open { grid-column: 2; grid-row: 1; }
.mine-parts { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.mine-parts li { font-size: 0.8rem; color: #a8b2d1; }
.mine-parts li::before { content: "☐ "; color: #8479a0; }
.board-card-facts .where { font-size: var(--fs-label); color: #8479a0; }

/* Whose a subtask is, small enough to sit on the line without crowding it. */
.check-owner {
    flex: none;
    min-width: 64px;
    max-width: 104px;
    font-size: var(--fs-label);
    padding: 2px 4px;
    background-color: rgba(0, 0, 0, 0.35);
    border-color: rgba(255, 255, 255, 0.2);
    color: #d8d2e8;
    cursor: pointer;
}
.card-owner-inferred { font-size: 0.88rem; color: #d8d2e8; }
.board-add input { flex: 1; min-width: 0; }

/* -------------------------------------------------------------- one card */

.card-panel { text-align: start; }
.card-panel-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.card-panel-head h3 { margin: 0; font-size: 1.05rem; color: #fff; }
/* A heading that is its own rename, like an item's title on a list: the
   cursor says it can be typed into, and it takes the row's width so the box
   it turns into has room. */
.editable-title {
    flex: 1;
    min-width: 0;
    cursor: text;
    unicode-bidi: plaintext;
    overflow-wrap: anywhere;
}
.editable-title:focus-visible { outline: 2px solid #8a2be2; outline-offset: 3px; border-radius: 4px; }
.title-edit { width: 100%; font: inherit; }
/* The pictures and the button that adds them are one thing, so they sit
   together: the shared album grid's 18px bottom margin left them looking like
   two unrelated parts of the panel. */
.card-panel .album-media { margin: 0 0 10px; }

.card-panel h4 {
    margin: 18px 0 8px;
    font-size: 0.8rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #8479a0;
}

.card-meta { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.card-field { display: flex; flex-direction: column; gap: 4px; font-size: 0.78rem; color: #a8b2d1; }
.card-field small { color: #8479a0; font-size: var(--fs-label); }
/* As tall as a dropdown beside it, so the box sits level with them rather
   than on their bottom edge. */
.card-field.card-field-check { flex-direction: row; align-items: center; gap: 6px; min-height: 35px; cursor: pointer; }
.card-field-check input[type="checkbox"] { margin: 0; width: auto; }

/* A bare `select` inherits the shared field rule, which is sized for something
   you type a sentence into. These are one word wide and sit in a row of them. */
.card-field select {
    background-color: rgba(0, 0, 0, 0.35);
    border-color: rgba(255, 255, 255, 0.2);
    color: #fff;
    padding: 7px 10px;
    font-size: 0.9rem;
    cursor: pointer;
}

/*
 * The two boxes you have to save by hand.
 *
 * `align-items: start` matters: the default stretches every child to the width
 * of the widest, which is what made a Save button wider than the box it saves.
 * The box is told to fill the row; the button is left at its own size.
 */
.card-details, .card-blocked {
    display: flex;
    flex-direction: column;
    align-items: start;
    gap: 8px;
    margin-top: 14px;
}
.card-details textarea, .card-blocked textarea {
    width: 100%;
    resize: vertical;
    /* The global textarea is content-box, so `width: 100%` was the panel plus
       22px of padding and border, hanging past its edge. And lib/fitbox.js
       sets a height that already counts both. */
    box-sizing: border-box;
}
/* Its own height, from lib/fitbox.js — not the global textarea's fixed 60px,
   which overrode the three rows it is asked for and made "auto" mean 60px. */
.card-details textarea { height: auto; line-height: 1.5; }

/* Nothing on screen said whether what you typed was stored. Now an unsaved
   change is visible in both places at once — the box you changed and the
   button that would save it — and the button is dead when there is nothing to
   save, so a disabled Save means "already saved". */
textarea.dirty { border-color: #ffb86b; background: rgba(255, 184, 107, 0.07); }
button.ghost.dirty {
    color: #ffb86b;
    border-color: rgba(255, 184, 107, 0.55);
    background: rgba(255, 184, 107, 0.12);
}
button.ghost.dirty:hover { background: rgba(255, 184, 107, 0.2); color: #ffd0a0; }

/* The tick on a checklist row and on a habit.
 *
 * It is a `button` rather than a checkbox because it carries a ✓ of its own and
 * has to be tappable at the size of a thumb. That means it inherits the big
 * pink button at the top of this sheet unless it is told otherwise — which is
 * how a tick came to render as a pink slab wider than the row it sits in. */
.check {
    flex: none;
    width: 22px;
    height: 22px;
    padding: 0;
    display: grid;
    place-items: center;
    font-size: 0.85rem;
    line-height: 1;
    font-weight: bold;
    color: #64ffda;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: 6px;
    box-shadow: none;
}
/* The inherited hover scales by 1.05, which on a 22px box next to text reads
   as a wobble rather than as a response. */
.check:hover { background: rgba(138, 43, 226, 0.35); border-color: #8a2be2; transform: none; }
.check:focus-visible { outline: 2px solid #8a2be2; outline-offset: 2px; }

.habit.done > .check,
.check-row.done .check,
.check.done {
    background: rgba(100, 255, 218, 0.15);
    border-color: rgba(100, 255, 218, 0.5);
}

.checks { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.check-row { display: flex; align-items: center; gap: 8px; font-size: 0.88rem; color: #d8d2e8; }
.check-row.done { opacity: 0.6; }
.check-row.done span { text-decoration: line-through; }
.check-row span { flex: 1; min-width: 0; }

/* A blocker is a link to another card, not a line of typed text — so unlike
   a checklist row there is nothing here to tick, only a title to show and a
   link to drop. The row still gets its own background and a left edge, the
   same shape a stay wears, so a list of one reads as a list rather than a
   stray line of text next to the free-text box below it. */
.card-blockers {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 6px;
    width: 100%;
}
.blocker-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.88rem;
    color: #d8d2e8;
    padding: 6px 10px;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.04);
    border-left: 2px solid #ff6b81;
}
.blocker-row.done { border-left-color: rgba(100, 255, 218, 0.5); }
.blocker-mark { flex: none; font-size: 0.8rem; }
.blocker-row.done .blocker-mark { color: #64ffda; }
.blocker-title { flex: 1; min-width: 0; }
.blocker-row.done .blocker-title { text-decoration: line-through; opacity: 0.7; }

/* The caption that keeps the free-text box from reading as an extension of
   the linked-task list right above it. */
.blocked-note-label {
    font-size: var(--fs-label);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #8479a0;
}

/* The same pink edge a blocker row wears, once there's a reason typed in —
   so a typed note and a linked task read as the same kind of fact. */
.card-blocked textarea.blocked-note-filled {
    border-left: 2px solid #ff6b81;
}

.card-blocker-add {
    display: flex;
    gap: 8px;
    width: 100%;
    margin-top: 4px;
}
.card-blocker-add select {
    flex: 1;
    min-width: 0;
}

/* "One more thing" sat flush against the last item, reading as part of the
   list rather than as the way to add to it. */
.card-checks .new-todo { margin-top: 14px; }

/* The heading turns when there is something in the box, so a card that is
   stuck says so from the top of the panel rather than only from its own text. */
.card-panel h4.is-blocked { color: #ff9aab; }

.card-comments { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }

/*
 * One comment: who, then what, with the time tucked bottom-right and the bin
 * top-right. The old row stacked a full-width "Amit · 14 Aug 19:20" line, the
 * text, and a full-size Remove button underneath — three rows of furniture for
 * one sentence of talk.
 */
.card-comment {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 8px;
    padding: 6px 10px 4px;
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    column-gap: 8px;
}
/* Who spoke and what they said were the same colour, so a run of comments read
   as one block of text with names scattered through it. The name is the label
   and takes the accent; the words are what you are reading and take the light. */
.card-comment .who {
    grid-column: 1;
    color: #ffb6c1;
    font-size: var(--fs-label);
    font-weight: 600;
    letter-spacing: 0.02em;
}
.card-comment p {
    grid-column: 1 / -1;
    margin: 2px 0 0;
    color: #ebe7f5;
    font-size: 0.92rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.card-comment .when {
    grid-column: 1 / -1;
    justify-self: end;
    color: #6f6790;
    font-size: var(--fs-label);
}

/* A bin rather than the word "Remove": it sits on every one of your own
   comments, and the word was as wide as some of the messages.
 *
 * It wears `ghost tiny` like the kit's own remove button, so it reads as a
 * button rather than as a stray glyph — and it stays neutral on hover. Red is
 * for something that has gone wrong, and removing your own comment has not. */
.card-comment .comment-drop {
    grid-column: 2;
    grid-row: 1;
    width: 26px;
    height: 24px;
    padding: 0;
    display: grid;
    place-items: center;
    font-size: var(--fs-label);
    line-height: 1;
    opacity: 0.75;
}
.card-comment .comment-drop:hover { opacity: 1; transform: none; }

/* ---------------------------------------------------------------- habits */

.habits-panel { text-align: start; }
.habit-list { display: grid; gap: 8px; }

.habit {
    display: flex;
    align-items: center;
    gap: 10px;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 8px;
    padding: 8px 10px;
}
.habit.done { border-color: rgba(100, 255, 218, 0.4); }
.habit.done .habit-title { text-decoration: line-through; opacity: 0.7; }

.habit-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    font-weight: normal;
    text-align: start;
}
/* The `background` was reset here but not the `transform`, so the inherited
   button's 5% grow was still running: pointing at a habit swelled its text and
   pushed it over the tick beside it. A row in a list does not respond by
   getting bigger — it lights up. */
.habit-body:hover { background: none; transform: none; }
.habit-body:hover .habit-title { color: #ffb6c1; }
.habit-body:focus-visible { outline: 2px solid #8a2be2; outline-offset: 2px; border-radius: 6px; }
.habit-title { color: #fff; font-size: 0.92rem; }
.habit-body small { color: #8479a0; font-size: var(--fs-label); }

/* The point of the whole habits board: a row of dots you can read at a
   glance without counting anything. */
.habit-dots { margin: 14px 0 6px; }
.habit-dot-strip { display: flex; flex-wrap: wrap; gap: 4px; }
/* A button, so a cycle can be put right long after it passed — every rule
   here is undoing what a button looks like everywhere else. */
.habit-dot {
    width: 11px;
    height: 11px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(255, 107, 129, 0.55);
    cursor: pointer;
    transition: transform 0.12s ease;
}
.habit-dot:hover { background: rgba(255, 107, 129, 0.85); transform: scale(1.35); }
.habit-dot.done:hover { background: #8bffe6; }
.habit-dot:focus-visible { outline: 2px solid #8a2be2; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
    .habit-dot:hover { transform: none; }
}
.habit-dot.done { background: #64ffda; }
.habit-dot.current { outline: 2px solid rgba(255, 255, 255, 0.5); outline-offset: 1px; }

.habit-settings { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }

@media (max-width: 560px) {
    .board-column { flex-basis: 82vw; }
}

/* ------------------------------------------------------- marks in the nav

   Two of them, and the difference is the point: 🔒 is a page that is one
   person's by design, 🚧 is one that simply is not finished. The second
   disappears by itself once the page is shared. */

.nav-mark {
    font-size: 0.7em;
    margin-inline-start: 4px;
    /* Sits with the text rather than on the baseline, where a glyph this size
       drags the whole link down by a pixel and the row stops looking level. */
    vertical-align: 0.08em;
    opacity: 0.85;
}

/* ---------------------------------------------------------- video embeds

   A link to YouTube, TikTok, Instagram or Facebook gets a player underneath
   it. Nothing is fetched from any of them until the button is pressed — see
   lib/embeds.js for why that matters more than the convenience does. */

.attachment.has-embed {
    flex-direction: column;
    align-items: stretch;
    flex-basis: 100%;
}

.embed {
    position: relative;
    margin-top: 8px;
    width: 100%;
    max-width: 480px;
    border-radius: 10px;
    overflow: hidden;
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(255, 255, 255, 0.12);
}

/* The two shapes these places actually publish. A reel forced into 16:9 is
   letterboxed to a sliver; a talk forced into 9:16 wastes most of a phone. */
.embed.shape-wide { aspect-ratio: 16 / 9; }
.embed.shape-tall { aspect-ratio: 9 / 16; max-width: 320px; }

.embed iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}

.embed-play {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 16px;
    background: none;
    border: none;
    box-shadow: none;
    color: #d8d2e8;
    font-weight: normal;
    text-align: center;
}
.embed-play:hover { background: rgba(255, 255, 255, 0.06); color: #fff; transform: none; }

.embed-label { font-size: 0.95rem; }
.embed-play small { font-size: var(--fs-label); color: #8479a0; max-width: 26ch; line-height: 1.4; }

/* A hint of each place's own colour, enough to tell them apart at a glance
   without pasting four logos into the page. */
.embed.provider-youtube { border-color: rgba(255, 0, 51, 0.35); }
.embed.provider-tiktok { border-color: rgba(105, 201, 208, 0.35); }
.embed.provider-instagram { border-color: rgba(221, 42, 123, 0.35); }
.embed.provider-facebook { border-color: rgba(24, 119, 242, 0.35); }

/* ================================================================== the map

   Leaflet draws its own controls; this only shapes the two things it does
   not — the canvas itself, and the popup content this app builds for every
   pin. The tiles are CARTO's Voyager basemap — light, on purpose, after the
   dark one this started with turned out too low-contrast to actually read.
   The canvas's own frame is what keeps a lighter map from looking like a
   hole punched in the page, rather than trying to darken the map itself. */

.map-canvas {
    height: min(70vh, 640px);
    margin-top: 16px;
    border-radius: 12px;
    overflow: hidden;
    border: 1px solid rgba(255, 255, 255, 0.1);
}

/* Leaflet's own popup chrome, themed to match the rest of the app rather than
   left as the library's default white card. Scoped under `.map-canvas`
   rather than left as a bare `.leaflet-popup-*` selector: Leaflet's own
   stylesheet is injected *after* this one (it only loads once the map tab is
   actually opened — see views/map.js), and at equal specificity the rule that
   loaded later wins. Being one selector more specific here is what makes this
   win regardless of load order, rather than by accident of timing. */
.map-canvas .leaflet-popup-content-wrapper, .map-canvas .leaflet-popup-tip {
    background: #1a1030;
    color: #e8e3f5;
}
.map-canvas .leaflet-popup-content-wrapper { border-radius: 10px; }
.map-canvas .leaflet-container a.leaflet-popup-close-button { color: #a8b2d1; }
.map-canvas .leaflet-container a.leaflet-popup-close-button:hover { color: #fff; }

.map-pin {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.4rem;
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5));
}

/* Cluster bubbles — the plugin's own default colours (green/yellow/orange)
   swapped for this app's palette, since MarkerCluster.Default.css is never
   loaded. Three sizes only in name: with a couple of dozen pins at most, this
   app will realistically only ever see "small". */
.marker-cluster { background-clip: padding-box; border-radius: 20px; }
.marker-cluster div {
    width: 30px;
    height: 30px;
    margin: 5px 0 0 5px;
    text-align: center;
    border-radius: 15px;
    font: 600 12px/30px inherit;
    color: #1a1030;
}
.marker-cluster-small     { background-color: rgba(100, 255, 218, 0.55); }
.marker-cluster-small div { background-color: rgba(100, 255, 218, 0.85); }
.marker-cluster-medium     { background-color: rgba(255, 184, 107, 0.55); }
.marker-cluster-medium div { background-color: rgba(255, 184, 107, 0.85); }
.marker-cluster-large     { background-color: rgba(255, 107, 129, 0.55); }
.marker-cluster-large div { background-color: rgba(255, 107, 129, 0.85); }

.map-popup { display: grid; gap: 8px; min-width: 200px; text-align: start; }
.map-popup strong { color: #fff; font-size: 1rem; }
/* Which trip a pin belongs to — a fact, not a form field, so it reads like
   the stay/meeting subtitle it replaced rather than like the address below it. */
.map-popup-meeting { margin: 0; color: #a8b2d1; font-size: 0.85rem; }
/* Same fact, but this pin's meeting has somewhere to go — the name itself is
   the way there, not a separate "Open" button taking up its own row. Not
   styled with its own idea of a link: `border-bottom` here is the exact same
   property, value, and hover rule as `.meeting-location`/`a.stay-name` use,
   so this is provably identical rather than a close approximation that could
   still render its underline differently.
   The actual glow every earlier pass at this missed: the bare `button`
   selector near the top of this file gives every button in the app a pink
   `box-shadow` and rounded corners for its own pill-button look — that glow
   was never part of this element's own styling, it leaked straight through
   because nothing here had reset it. `appearance: none` matters too: without
   it, a <button> keeps its native background/highlight on a phone regardless
   of `background: none`. `justify-self: start` keeps the box no wider than
   the text, so nothing here can ever poke past the popup's own rounded edge
   the way an earlier full-width version of this button did. */
button.map-popup-meeting-link {
    justify-self: start;
    max-width: 100%;
    margin: 0;
    border: none;
    border-bottom: 1px dashed rgba(255, 182, 193, 0.45);
    border-radius: 0;
    outline: none;
    background: none;
    box-shadow: none;
    appearance: none;
    -webkit-appearance: none;
    padding: 0 0 3px;
    font: inherit;
    font-weight: normal;
    line-height: 1.4;
    text-align: start;
    color: #ffb6c1;
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transform: none;
}
button.map-popup-meeting-link::-moz-focus-inner { border: 0; padding: 0; }
button.map-popup-meeting-link:hover,
button.map-popup-meeting-link:focus-visible {
    color: #fff;
    border-bottom-color: #fff;
}
.map-popup-address {
    margin: 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    color: #d8d2e8;
    font-size: 0.82rem;
}
/* A trip's own meeting line and its own editable note, on a merged place
   pin — one box, tighter than the popup's usual row gap, so the note reads
   as belonging to the line right above it rather than sitting equidistant
   from that one and the next trip's line below it. The link stays its own
   width (align-items default) while the textarea and its Save button
   stretch to the popup's full width, same as a normal place popup's do. */
.map-popup-trip { display: flex; flex-direction: column; gap: 4px; }
.map-popup-trip > button { align-self: flex-start; }
.map-popup-actions { display: flex; gap: 8px; }
/* One button per trip sharing a pin, stacked rather than in a row — a city
   visited five times needs room to list five names, not five squeezed tabs. */
.map-popup-links { display: flex; flex-direction: column; gap: 6px; }
.map-popup-links button { text-align: start; }

.map-popup-status { margin: 0; min-height: 1.1em; font-size: 0.8rem; color: #ffb86b; }

/* ======================= lists that are for something in particular ====== */
/*
 * A watchlist and a list of things to order are ordinary lists with a second
 * line under each row. Everything above that line — the tick, the title, the
 * drag handle, the expand button — is untouched, which is why almost nothing
 * here overrides an existing rule.
 */

.chip-kind { font-size: 0.85rem; line-height: 1; }

/* The one number a padlocked list may show. Pink rather than the usual black
   pill, because it means the opposite of a count: these are the items you *can*
   read, on a chip that otherwise says you can't read anything. */
.chip-count.revealed {
    background: rgba(255, 182, 193, 0.18);
    color: #ffb6c1;
}

.list-kind {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 9px 10px;
    font-family: inherit;
    font-size: 0.9rem;
}
.list-kind:focus { border-color: #8a2be2; outline: none; }

/* --------------------------------------------------- the second line */

.row-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    /* Lined up with the title above rather than with the row's edge: the tick
       and the drag handle sit in that space, and a fact starting under them
       reads as belonging to the handle. */
    padding: 4px 0 0 30px;
    font-size: 0.78rem;
    color: #8479a0;
}
.todo.done .row-meta { opacity: 0.7; }

.fact { white-space: nowrap; }
.fact.stopped { color: #ffb6c1; }
.fact.state-ordered { color: #ffb86b; }
.fact.state-arrived { color: #7fd6a0; }

.score {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: rgba(0, 0, 0, 0.3);
    border-radius: 999px;
    padding: 2px 9px;
    color: #d8d2e8;
    text-decoration: none;
    white-space: nowrap;
}
a.score:hover { background: rgba(255, 255, 255, 0.14); color: #fff; }

/* ------------------------------------------- the three-state order button */

.order-state {
    flex: none;
    width: 30px;
    height: 30px;
    padding: 0;
    border-radius: 8px;
    font-size: 1rem;
    line-height: 1;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    box-shadow: none;
    cursor: pointer;
}
.order-state:hover { transform: none; background: rgba(255, 255, 255, 0.14); }
.order-state.ordered { border-color: rgba(255, 184, 107, 0.6); }
.order-state.arrived { border-color: rgba(127, 214, 160, 0.6); }

/* ------------------------------------- the kind's own controls, when open */

.kind-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 10px;
}

.mini-field { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-label); color: #8479a0; }
.mini-field input {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: 8px;
    padding: 7px 10px;
    font-family: inherit;
    font-size: 0.9rem;
    outline: none;
    box-sizing: border-box;
    /* Wide enough for "1:23:45" and for a tracking number, and allowed to give
       way on a narrow phone rather than pushing the panel sideways. */
    width: 100%;
    min-width: 0;
}
.mini-field input[type="number"] { max-width: 90px; }
.mini-field:has(input[type="text"]), .mini-field:has(input[type="url"]) { flex: 1 1 180px; }
.mini-field input:focus { border-color: #8a2be2; }

.order-controls .hint { flex: 1 1 100%; margin: 0; font-size: var(--fs-label); }

/* --------------------------------------------- picking which film it is */

.watch-suggestions, .watch-candidates {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    text-align: start;
}
.watch-suggestions button, .watch-candidates button {
    width: 100%;
    text-align: start;
    unicode-bidi: plaintext;
}
.watch-finder { flex: 1 1 100%; }
.watch-finder .hint { margin: 8px 0 0; font-size: var(--fs-label); }

/* Saying what to look for, rather than being stuck with what the row is called.
   A film's full name is often the one string that finds only its stub entry. */
.watch-look { display: flex; gap: 6px; margin-top: 8px; }
.watch-query {
    flex: 1 1 auto;
    min-width: 0;
    padding: 6px 8px;
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.25);
    color: #ece8f5;
    font: inherit;
    font-size: 0.8rem;
    unicode-bidi: plaintext;
}
.watch-query:focus { outline: none; border-color: #8a2be2; }
.watch-look button { flex: none; }

/* The entry the row is on now. Marked because the offers for a film with a
   duplicate look almost identical, and "not that one" needs to know which. */
.watch-candidates button.current {
    border-color: rgba(138, 43, 226, 0.6);
    color: #fff;
}

/* ------------------------------------ letting something out of a secret list */

/* Let out of a private list of yours. Same reasoning as the kit's marker above
   — it was too faint to notice, and a marker nobody notices is decoration. */
.todo.revealed {
    border-color: rgba(255, 182, 193, 0.55);
    background:
        linear-gradient(90deg, rgba(255, 182, 193, 0.22), rgba(255, 182, 193, 0.06) 70%);
    box-shadow: 0 0 16px rgba(255, 182, 193, 0.18);
}

/* One item on a shared list kept back from the other person. The row is there
   for both of you — a list you cannot count is a list you cannot trust — and
   only the words are missing, so the placeholder reads as a fact rather than
   as something failing to load. It is drawn by the kit's own rule — see
   `.kit-item.secret` — so a secret looks the same on both pages. */
.todo-secret-toggle[hidden] { display: none; }
.reveal.is-revealed { color: #ffb6c1; }
/* The tick on a finished one keeps the plain mark; only the eye is a badge. */
.revealed-list .reveal-mark { width: auto; }

/* The read-only copy shown to the person the list is *not* for. No tick, no
   handle, nothing to open — revealing an item shares it rather than handing the
   list over, and a row that looked editable would be promising otherwise. */
.revealed-list { margin-top: 18px; text-align: start; }
.revealed-list .todo-title { cursor: default; }
.reveal-mark { flex: none; width: 20px; text-align: center; color: #ffb6c1; }

/* ------------------------------------------------------------- the notes board

   Sticky notes, so they are actually coloured — the one place in this app where
   a saturated block of colour is the point rather than an accent. The text on
   them is dark for the same reason: it is paper, and paper is not backlit.

   The five colours live in custom properties on `.sticky-<name>`, set once and
   read by the tile, by the editor behind an open note, and by the swatch that
   chooses it — so a colour cannot come to mean one thing in one place and
   something else in another. */

.sticky-butter { --sticky: #f6e08a; --sticky-edge: #d9c05c; }
.sticky-mint   { --sticky: #a9e0bd; --sticky-edge: #7cc296; }
.sticky-sky    { --sticky: #a8cef0; --sticky-edge: #7aabd6; }
.sticky-rose   { --sticky: #f3b3c4; --sticky-edge: #d8899f; }
.sticky-lilac  { --sticky: #cbb8ec; --sticky-edge: #a992d2; }

.sticky-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: center;
    justify-content: center;
    margin-top: 16px;
}
.sticky-actions .archived-toggle { margin-top: 0; width: auto; }

/* auto-fill rather than auto-fit: with one note on the board, auto-fit would
   stretch it the full width of the page, which reads as a banner and not as a
   note somebody stuck up. */
.sticky-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 14px;
    margin-top: 20px;
    text-align: start;
}

.sticky {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-height: 150px;
    padding: 14px;
    background: var(--sticky);
    color: #221c33;
    border: none;
    border-bottom: 3px solid var(--sticky-edge);
    border-radius: 4px 4px 6px 6px;
    box-shadow: 0 6px 14px rgba(0, 0, 0, 0.35);
    font-weight: normal;
    font-size: 0.92rem;
    text-align: start;
    cursor: pointer;
}
.sticky:hover { transform: translateY(-2px); background: var(--sticky); }
.sticky.cleared { opacity: 0.45; }

.sticky-title {
    font-size: 1rem;
    line-height: 1.3;
    unicode-bidi: plaintext;
    overflow-wrap: anywhere;
}

/* Newlines are how somebody writes a list on a note, and collapsing them would
   turn one into a paragraph. */
.sticky-text {
    flex: 1;
    margin: 0;
    line-height: 1.45;
    white-space: pre-wrap;
    unicode-bidi: plaintext;
    overflow-wrap: anywhere;
}
.sticky-blank { font-style: italic; opacity: 0.55; }

.sticky-foot { font-size: var(--fs-label); opacity: 0.65; }

/* ------------------------------------------------------------- one note open */

.sticky-editor {
    width: min(560px, 92vw);
    max-height: 88vh;
    padding: 18px;
    border: none;
    border-radius: 10px;
    background: var(--sticky, #f6e08a);
    color: #221c33;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55);
    overflow-y: auto;
}
.sticky-editor::backdrop { background: rgba(8, 3, 18, 0.72); }

/* The fields are the note, not boxes sitting on it: the paper shows through,
   and only a thin rule says where you may type. The base field rule is a dark
   slab, which on this background would be a hole. */
.sticky-editor #stickyTitle,
.sticky-editor #stickyBody {
    display: block;
    width: 100%;
    box-sizing: border-box;
    background: rgba(255, 255, 255, 0.35);
    border: 1px solid rgba(0, 0, 0, 0.18);
    border-radius: 6px;
    color: #221c33;
    font-family: inherit;
    padding: 9px 11px;
}
.sticky-editor #stickyTitle { font-size: 1rem; font-weight: bold; }
.sticky-editor #stickyBody {
    margin-top: 10px;
    font-size: 0.95rem;
    line-height: 1.5;
    resize: vertical;
    min-height: 200px;
}
.sticky-editor #stickyTitle::placeholder,
.sticky-editor #stickyBody::placeholder { color: rgba(34, 28, 51, 0.45); }
.sticky-editor #stickyTitle:focus,
.sticky-editor #stickyBody:focus { border-color: rgba(0, 0, 0, 0.45); outline: none; }

.sticky-colours { display: flex; gap: 8px; margin-top: 10px; }

.sticky-swatch {
    width: 26px;
    height: 26px;
    padding: 0;
    border-radius: 50%;
    background: var(--sticky);
    border: 2px solid rgba(0, 0, 0, 0.2);
    box-shadow: none;
}
.sticky-swatch:hover { background: var(--sticky); transform: scale(1.12); }
/* A ring outside the swatch rather than a tick inside it: at 26px a glyph is
   unreadable, and the colour is the whole content of the button. */
.sticky-swatch.chosen {
    border-color: #221c33;
    box-shadow: 0 0 0 2px var(--sticky), 0 0 0 4px #221c33;
}

.sticky-meta { margin: 12px 0 0; font-size: 0.78rem; opacity: 0.65; }

.sticky-editor-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
    margin-top: 14px;
}

/* The ghost buttons are built for the dark page; on paper they would be a pale
   smudge on a pale note. */
.sticky-editor button.ghost {
    background: rgba(0, 0, 0, 0.1);
    border-color: rgba(0, 0, 0, 0.25);
    color: #221c33;
}
.sticky-editor button.ghost:hover { background: rgba(0, 0, 0, 0.18); }

.sticky-editor .status { color: #4a3f66; }
.sticky-editor .status.warn { color: #8c1d3f; }

@media (prefers-reduced-motion: reduce) {
    .sticky:hover, .sticky-swatch:hover { transform: none; }
}

/* --------------------------------------------------------- who wrote what

   One shade per person, decided by their position in the list the server sends
   with the board — so the same person is the same shade on both our screens,
   which is the only thing that makes "the grey line" mean anything between us.

   Both are dark, because this is ink on paper, and they are told apart by
   weight rather than by hue: a note is not a chart, and colour-coding it in red
   and blue would make a shopping list look like an argument. */

.sticky-ink-0 { color: #1c1730; }
.sticky-ink-1 { color: #6c6486; }

/* Each line is its own block, so a note keeps the shape it was typed in. The
   trailing space in an empty line gives it a height to occupy. */
.sticky-line { display: block; white-space: pre-wrap; }

/* The open note, being read rather than typed into. */
.sticky-reading {
    margin-top: 10px;
    padding: 9px 11px;
    min-height: 200px;
    max-height: 46vh;
    overflow-y: auto;
    border: 1px solid transparent;
    border-radius: 6px;
    font-size: 0.95rem;
    line-height: 1.5;
    unicode-bidi: plaintext;
    overflow-wrap: anywhere;
    cursor: text;
}
/* It is the way into editing, so it has to look like somewhere you may type. */
.sticky-reading:hover { border-color: rgba(0, 0, 0, 0.18); background: rgba(255, 255, 255, 0.2); }

.sticky-ink {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin: 10px 0 0;
    font-size: var(--fs-label);
}
/* A dot in the person's own ink, ahead of their name — the key and the thing
   it explains are the same two classes, so they cannot come to disagree. */
.sticky-ink-key::before { content: "▪ "; }

/* Erasing one for good is the only thing in this app that really destroys
   something, so it does not look like the buttons beside it. */
.sticky-editor button.ghost.danger { color: #8c1d3f; border-color: rgba(140, 29, 63, 0.4); }
.sticky-editor button.ghost.danger:hover { background: rgba(140, 29, 63, 0.14); }

/* ------------------------------------------------ left out of this trip

   Not everything is needed for every meeting. An excluded item keeps its row —
   its place, its priority, whose it is — and steps out of the way until the
   next unpack puts it back, so this reads as "standing aside" rather than as
   "finished with", which is what `.packed` already means. */

.kit-item.set-aside {
    opacity: 0.5;
    border-left-style: dashed;
}
/* The title is not struck through: nothing here has been dealt with, and a line
   through it would say it had. */
.kit-item.set-aside .kit-title { font-style: italic; }

/* The control that puts it aside, and the one that brings it back. */
.kit-aside.on { color: #ffb6c1; }

/* Who else has this note open right now. A live fact, so it gets a dot that
   says "now" the way the call badge does, rather than sitting there looking
   like another line of the note's own history. */
.sticky-here {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 12px 0 0;
    font-size: 0.8rem;
    font-weight: bold;
}
.sticky-here::before {
    content: "";
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #2f9e5c;
    box-shadow: 0 0 0 3px rgba(47, 158, 92, 0.25);
    animation: stickyHerePulse 2s ease-in-out infinite;
}

@keyframes stickyHerePulse {
    50% { box-shadow: 0 0 0 6px rgba(47, 158, 92, 0.12); }
}

@media (prefers-reduced-motion: reduce) {
    .sticky-here::before { animation: none; }
}

/* ------------------------------------------------------------- the album page

   Every picture in the app on one wall. The tiles are the same `.album-tile`
   the meetings page draws — see lib/tiles.js — so only what is around them is
   new here. */

.wall-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-bottom: 4px;
}
/* The shared toggle centres itself and takes a whole row, which is right on a
   page where it is the only control and wrong beside two buttons. */
.wall-actions .archived-toggle { margin-top: 0; width: auto; justify-content: flex-start; }

/* Spans the grid rather than sitting in a cell: it is a line over the run of
   pictures under it, not one of them. */
.wall-heading {
    grid-column: 1 / -1;
    margin: 14px 0 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: #d8d2e8;
    unicode-bidi: plaintext;
}
.wall-heading:first-child { margin-top: 0; }

.wall-more { display: flex; justify-content: center; margin-bottom: 18px; }

/* Which trip a picture is from, in the lightbox. Wraps rather than squeezing
   the dropdown: a trip's name is as long as the place it happened in. */
.wall-trip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 8px;
}
.wall-trip-label { font-size: 0.85rem; color: #bdb4d4; }
.wall-trip select { max-width: min(60vw, 260px); }

/* ---------------------------------------------------------------
   Arranging the bar, on the admin page. The same drag handle and
   the same keyboard moves as the lists strip and the kit, because
   it is the same lib/dragorder.js underneath.
   --------------------------------------------------------------- */

.nav-order {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.nav-order-row {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 48px;
    padding: 0 12px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
}

/* The four that actually reach the bar, told apart by more than their
   position in the list — the line below says where the boundary is, and this
   says which side of it you are looking at without counting. */
.nav-order-row.in-bar {
    background: rgba(255, 182, 193, 0.1);
    border-color: rgba(255, 182, 193, 0.3);
}

.nav-order-icon { font-size: 1.1rem; line-height: 1; }
.nav-order-name { flex: 1; min-width: 0; font-size: var(--fs-small); }

.nav-order-note {
    font-size: var(--fs-label);
    color: #8479a0;
    white-space: nowrap;
}

/* Where the bar ends. A caption on a rule rather than a row of its own with a
   background, so it reads as the boundary between two groups and not as an
   eleventh page you could drag. */
.nav-order-line {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 6px 2px;
    font-size: var(--fs-label);
    color: #8479a0;
    white-space: nowrap;
}
.nav-order-line::before,
.nav-order-line::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgba(255, 255, 255, 0.12);
}

.nav-order-actions { margin-top: 12px; }

/* =================================================================
   Planning a trip — the plan on a meeting, and the World wishlist.
   ================================================================= */

/* A day of the plan: its heading, then its places as ordinary rows. */
.plan-day { display: flex; flex-direction: column; gap: 8px; }
.plan-day + .plan-day { margin-top: 6px; padding-top: 10px; border-top: 1px solid rgba(255, 255, 255, 0.07); }
.plan-day-title {
    margin: 0;
    font-size: var(--fs-small);
    font-weight: 600;
    color: #ffb6c1;
}
.plan-unscheduled .plan-day-title { color: #b9aed4; }
.plan-empty { margin: 0; color: #8479a0; font-size: var(--fs-label); font-weight: 400; }
.plan-day.is-empty .plan-day-title { color: #8479a0; }
.plan-day.is-empty + .plan-day.is-empty { margin-top: 0; padding-top: 4px; border-top: none; }
.plan-list { margin: 0; }

/* Under a place: the tick, the day and the time, and TripAdvisor. */
.plan-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: 4px;
}
.plan-tick { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-small); color: #d8d2e8; cursor: pointer; }
.plan-day-pick, .plan-time { font-size: var(--fs-small); padding: 4px 8px; width: auto; }
.plan-time:disabled { opacity: 0.45; }
.plan-went { color: #7fd6a0; font-size: var(--fs-small); font-weight: 600; }
.plan-ta { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.plan-ta .hint { margin: 0; font-size: var(--fs-label); }
.ta-link { color: #34e0a1; }
.ta-paste { display: inline-flex; gap: 6px; }
.ta-paste input { width: min(60vw, 320px); font-size: var(--fs-small); padding: 4px 8px; }

/* The card that wraps a trip up: the one thing on the page asking for a
   decision, so it looks like it. */
.plan-wrap {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px 14px;
    border: 1px solid rgba(255, 182, 193, 0.45);
    border-radius: 10px;
    background: rgba(255, 182, 193, 0.08);
}
.plan-wrap .hint { margin: 0; text-align: start; }
.plan-wrap-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.plan-wrap-list li { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.plan-wrap-list select { width: auto; font-size: var(--fs-small); padding: 4px 8px; }
.plan-wrap > button { align-self: flex-start; }

.plan-wrapped { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; color: #8479a0; font-size: var(--fs-small); }

.plan-suggestions {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px 12px;
    border: 1px dashed rgba(255, 255, 255, 0.18);
    border-radius: 10px;
}
.plan-suggestions-title {
    align-self: flex-start;
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    color: #b9aed4;
    font-size: var(--fs-small);
    font-weight: 600;
    cursor: pointer;
}
.plan-suggestions ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.plan-suggestions li { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: var(--fs-small); }

/* The World wishlist. */
.wish-search { margin: 0 0 16px; position: relative; }
.wish-search input { width: 100%; box-sizing: border-box; }
.wish-body { display: flex; flex-direction: column; gap: 18px; }
.wish-country { display: flex; flex-direction: column; gap: 8px; }
.wish-town { margin: 4px 0 0; font-size: var(--fs-small); font-weight: 600; color: #ffb6c1; }
.wish-star { filter: grayscale(1); opacity: 0.5; }
.wish-star.on { filter: none; opacity: 1; }
.stay.must-go { border-color: rgba(255, 214, 102, 0.45); }
.wish-plan { width: auto; font-size: var(--fs-small); padding: 4px 8px; }
.wish-body .hint, .plan-controls .hint { text-align: start; margin: 0; }
