/*
   KeyCodeMart - the landing page
   ==============================

   WHY THIS IS A SEPARATE FILE. app.css is the working application: the till,
   the grids, the permission matrix. The landing page is the one screen a
   stranger sees and the only one whose job is persuasion rather than data
   entry, and it wants larger type, more air and components that appear
   nowhere else. Mixing the two meant every landing tweak risked a purchase
   bill, and this file is loaded only by Views/Home/Index.cshtml.

   NO COLOUR IS NAMED HERE. Everything comes from theme.css, exactly as in
   app.css - so all six palettes and both rooms work with no rule below
   knowing they exist. If a theme change ever needs a rule here edited, that
   rule has a hard-coded colour and that is the bug.

   EVERYTHING IS SCOPED UNDER .lp. The class sits on a wrapper inside the
   layout's <main>, so nothing here can reach the topbar, the footer, the
   sign-in pages or any working screen.

   Read in order: the shell and rhythm, the section nav, the hero and its
   product mockup, then the sections down the page, then the media queries -
   which are all at the foot rather than scattered, so what happens on a phone
   can be read in one place.
*/

/* ══════════════════════════════════════════════ the shell and the rhythm ══ */

/*  ONE SECTION RHYTHM, and every band on the page uses it. The old page set
    its own padding per section and they had drifted apart, which is most of
    what made it read as long: an eye going down a page reads inconsistent
    gaps as "more stuff", not as "different stuff".  */
/*  THE SECTION STEP IS PAID TWICE, and that is what made the page gappy.

    It is padding, not margin, so every boundary between two sections costs
    the value BELOW the first plus the value ABOVE the second. At 6rem that
    was 96 + 96 = 192px of empty ground between one band and the next, and
    96px between the section nav and the first word of the hero - which is
    what reads as the page falling apart rather than breathing.

    2.5rem gives 40px a side, so a boundary is 80px: still clearly a change of
    subject, less than half of what it was. The clamp's middle term is
    anchored in rem rather than left as pure vw, so it stops growing when the
    column holding it stops.  */
.lp {
    /*  Halved again at the owner's asking: 1.5rem a side, so a boundary is
        48px - a change of subject, not a change of page.  */
    --lp-section: clamp(1rem, .8rem + .7vw, 1.5rem);
    --lp-card-pad: clamp(1.1rem, .8rem + 1vw, 1.5rem);

    /*  The measure for anything that is read rather than scanned. Prose past
        about 70 characters makes the eye lose its place on the return sweep. */
    --lp-measure: 42rem;
}

.lp section { padding-block: var(--lp-section); }

/*  The anchored sections clear both sticky bars. Derived from the header
    token so it stays right when the bar changes height.  */
.lp [id] { scroll-margin-top: calc(var(--header-h) + 3.5rem); }

/*  ── section headings ─────────────────────────────────────────────────────
    An eyebrow, a heading and at most one line under it. The old page ran two
    or three explanatory paragraphs before several sections; that is
    documentation, and a visitor scanning has already gone past it.  */

/*  The step from a heading to the thing it introduces. Smaller than the step
    between sections, so the heading stays attached to its own content rather
    than floating between two of them.  */
.lp-head { max-width: var(--lp-measure); margin-bottom: clamp(.8rem, .7rem + .5vw, 1.1rem); }
.lp-head.is-centred { margin-inline: auto; text-align: center; }

.lp-eyebrow {
    display: inline-block;
    font-size: .6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .1em;
    color: var(--accent-ink);
    background: var(--accent);
    border-radius: var(--radius-pill);
    padding: .3rem .7rem;
    margin-bottom: .9rem;
}

/*  A quieter eyebrow for sections further down, where a solid accent chip
    every other band would turn the page into a row of badges.  */
.lp-eyebrow.is-quiet {
    color: var(--ink-soft);
    background: transparent;
    border: 1px solid var(--line);
    padding-inline: .65rem;
}

.lp-h {
    font-size: var(--fs-2xl);
    line-height: 1.2;
    letter-spacing: -.022em;
    margin: 0 0 .6rem;
    text-wrap: balance;
}

.lp-sub {
    margin: 0;
    font-size: var(--fs-lg);
    line-height: 1.6;
    color: var(--ink-soft);
    max-width: var(--lp-measure);
}
.lp-head.is-centred .lp-sub { margin-inline: auto; }

/* ═══════════════════════════════════════════════════ the section nav ══ */
/*  ANCHORS, NOT ROUTES. It is one page, and a menu that reloads to move down
    the same page is slower and loses the reader's place.

    It sits under the layout's topbar rather than inside it. The topbar is
    shared with the sign-in and register screens, and section anchors on a
    sign-in page point at sections that are not there.  */

.lp-nav {
    position: sticky;
    top: var(--header-h);
    z-index: 15;
    /*  SOLID, NOT BLURRED. A translucent bar with a backdrop blur, pinned
        under another pinned bar, is what made the row shimmer as the page
        scrolled under it: the compositor re-blurs on every frame and the
        letters visibly tremble. An opaque ground and a hairline do the same
        job and stay perfectly still.  */
    background: var(--raised);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 1px 2px rgb(0 0 0 / .03);
    padding-block: .45rem;
}

.lp-nav-in { display: flex; align-items: center; gap: .3rem; }

/*  Each anchor is a pill with its own mark: the icon sits in a soft accent
    disc, the word beside it. Hover lifts the pill on to an accent tint with
    the disc turning solid; the section being read is the filled pill.  */
.lp-nav a {
    display: inline-flex; align-items: center; gap: .45rem;
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--ink);
    padding: .35rem .85rem .35rem .45rem;
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    white-space: nowrap;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
                border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
.lp-nav a .ico {
    width: 1.5rem; height: 1.5rem; padding: .3rem;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
    flex: none;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.lp-nav a:hover {
    background: var(--accent-soft);
    color: var(--ink);
    border-color: color-mix(in srgb, var(--accent) 35%, transparent);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 18%, transparent);
    /*  No lift here on purpose: a pill that moves under the pointer can hand
        the pointer to its neighbour and flicker between the two.  */
    text-decoration: none;
}
.lp-nav a:hover .ico { background: var(--accent); color: var(--accent-ink); }

/*  The section being read. A tint and full-strength ink rather than accent
    text: accent on accent-soft measures 4.08:1 in the day palettes, under the
    4.5:1 floor. The tint alone is what says "here" - the colour never was.  */
.lp-nav a.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 30%, transparent); }
.lp-nav a.on .ico { background: color-mix(in srgb, var(--accent-ink) 22%, transparent); color: var(--accent-ink); }

/*  ── the same nav on a phone ──────────────────────────────────────────────
    A disclosure rather than a row that scrolls sideways. Seven anchors do not
    fit across a 360px screen, and a strip that has to be swiped hides half
    its own contents behind a gesture nobody is told about.

    <details> rather than script: it opens with no JavaScript, closes on Esc,
    and is reachable from the keyboard for free. A menu that fails to open
    because a file did not load is worse than one that was never collapsible. */

.lp-nav-more { display: none; position: relative; }

.lp-nav-more > summary {
    display: inline-flex; align-items: center; gap: .5rem;
    font-size: var(--fs-sm); font-weight: 600;
    color: var(--ink-soft);
    padding: .45rem .8rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-btn);
    cursor: pointer;
    list-style: none;
}
.lp-nav-more > summary::-webkit-details-marker { display: none; }
.lp-nav-more > summary::marker { content: ""; }
.lp-nav-more[open] > summary { border-color: var(--accent); color: var(--ink); }

/*  Drawn from a border rather than a glyph, so it rotates cleanly and needs
    no icon font - the same chevron the working menu uses.  */
.lp-nav-more .chev {
    width: .4rem; height: .4rem;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    transition: transform var(--dur) var(--ease);
}
.lp-nav-more[open] .chev { transform: rotate(-135deg); }

.lp-nav-pop {
    position: absolute;
    top: calc(100% + .4rem);
    left: 0;
    min-width: 12rem;
    display: grid;
    gap: 1px;
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    padding: .3rem;
    z-index: 30;
}
.lp-nav-pop a { display: flex; border-radius: var(--radius-sm); }

/* ═══════════════════════════════════════════════════════════ the hero ══ */

.lp-hero {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
    gap: clamp(2rem, 1rem + 4vw, 4rem);
    align-items: center;

    /*  Its own top step, and a small one: the hero sits directly under the
        section nav, and a large gap there is the first thing anybody sees.  */
    padding-block: clamp(1.25rem, 1rem + 1vw, 2rem) var(--lp-section);
}

/*  ONE h1 ON THE PAGE, and it is this one. Held to 2.9rem rather than left to
    grow with the viewport: a heading sized in vw keeps growing after the
    column holding it has stopped, and the words then outrun their own column.
    The clamp's middle term is anchored in rem for the same reason.  */
/*  ONE h1 ON THE PAGE, and it takes the top step of the shared scale rather
    than a size of its own. It used to stop growing at about 1100px, so a
    laptop and a large monitor showed it identically - large on the first and
    unremarkable on the second.  */
.lp-hero h1 {
    /*  The one script line on the site: Lobster, with the system cursive
        behind it. Wider tracking than the sans headings, because a script
        face packs its letters already; the weight is the face's own.  */
    font-family: "Lobster", "Brush Script MT", "Segoe Script", cursive;
    font-weight: 400;
    font-size: calc(var(--fs-3xl) * 1.08);
    line-height: 1.18;
    letter-spacing: .005em;
    color: var(--ink);
    margin: 0 0 1rem;
    text-wrap: balance;
}

.lp-lead {
    font-size: var(--fs-lg);
    line-height: 1.6;
    color: var(--ink-soft);
    margin: 0 0 1.75rem;
    max-width: 32rem;
}

.lp-cta { display: flex; flex-wrap: wrap; gap: .75rem; }

/*  The trust line. Not a claim - just the five words that say what kind of
    software this is, for somebody who read the heading and is still deciding
    whether to keep reading.  */
.lp-trust {
    display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
    margin: 1.5rem 0 0;
    font-size: .8125rem;
    color: var(--ink-soft);
}
.lp-trust .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--ink-faint); }

/* ── the product mockup ───────────────────────────────────────────────────
   AN ILLUSTRATION, AND MARKED AS ONE. It is aria-hidden with a visually
   hidden caption beside it, because a screen reader announcing invented sales
   figures as though they were the visitor's own data is worse than silence.

   Built from the product's own tokens rather than a screenshot: a picture
   would be one palette out of six and would go stale the first time a screen
   changed. This follows the theme the visitor picked, which is the point the
   section further down is making anyway.  */

/*  THE CHIPS HANG OFF THE FRAME, so they cannot live inside it: the frame
    clips its own corners with overflow:hidden, which would cut them in half.
    The wrapper is what they are positioned against.  */
.lp-shot-wrap { position: relative; }

.lp-shot {
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

/*  Window chrome. Three dots and a title is the shorthand everybody already
    reads as "this is an application", and it costs eleven pixels of height. */
.lp-shot-bar {
    display: flex; align-items: center; gap: .5rem;
    padding: .6rem .85rem;
    border-bottom: 1px solid var(--line);
    background: var(--sunken);
}
.lp-shot-dots { display: flex; gap: .3rem; }
.lp-shot-dots i { width: .5rem; height: .5rem; border-radius: 50%; background: var(--line-strong); }
.lp-shot-title { font-size: .6875rem; font-weight: 600; color: var(--ink-faint); letter-spacing: .02em; }

.lp-shot-body { padding: var(--lp-card-pad); display: grid; gap: .85rem; }

.lp-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .6rem; }

.lp-stat {
    background: var(--ground);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: .65rem .7rem;
}
.lp-stat span { display: block; font-size: .625rem; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-soft); }
.lp-stat b { display: block; font-family: var(--font-num); font-size: 1.0625rem; letter-spacing: -.02em; margin-top: .15rem; }
.lp-stat.is-warn b { color: var(--warn); }

/*  A chart drawn with grid rows rather than an SVG or a library. Twelve divs
    and a height each - nothing to download, and it repaints in the new
    palette when the theme changes.  */
.lp-chart {
    display: flex; align-items: flex-end; gap: .3rem;
    height: 4.5rem;
    padding: .7rem;
    background: var(--ground);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}
/*  THE BAR HEIGHTS ARE HERE AND NOT ON THE ELEMENTS. Twelve inline style
    attributes in the Razor would be twelve numbers in the markup that no
    stylesheet knows about, and the brief is explicit about not scattering
    inline styles. They are decoration, so they belong in the decoration.  */
.lp-chart i {
    flex: 1 1 0;
    background: var(--accent);
    border-radius: 2px 2px 0 0;
    opacity: .35;
}
.lp-chart i:nth-child(1)  { height: 38%; }
.lp-chart i:nth-child(2)  { height: 52%; }
.lp-chart i:nth-child(3)  { height: 44%; }
.lp-chart i:nth-child(4)  { height: 61%; }
.lp-chart i:nth-child(5)  { height: 55%; }
.lp-chart i:nth-child(6)  { height: 72%; }
.lp-chart i:nth-child(7)  { height: 64%; }
.lp-chart i:nth-child(8)  { height: 80%; }
.lp-chart i:nth-child(9)  { height: 70%; }
.lp-chart i:nth-child(10) { height: 88%; }
.lp-chart i:nth-child(11) { height: 79%; }
.lp-chart i:nth-child(12) { height: 100%; }
.lp-chart i:nth-child(n+9) { opacity: .8; }
.lp-chart i:last-child { opacity: 1; }

.lp-rows { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.lp-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: .75rem;
    align-items: center;
    padding: .5rem .7rem;
    background: var(--raised);
    font-size: .75rem;
}
.lp-row span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--ink-soft); }
.lp-row b { font-family: var(--font-num); font-weight: 600; }
.lp-row em { font-style: normal; font-size: .6875rem; color: var(--ink-faint); font-family: var(--font-num); }

/*  The floating chips. Two, not five - they are punctuation on the mockup,
    and a mockup wearing a ring of labels stops looking like software.  */
.lp-chip {
    position: absolute;
    display: inline-flex; align-items: center; gap: .4rem;
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow);
    padding: .4rem .75rem;
    font-size: .75rem;
    font-weight: 600;
}
.lp-chip .tick { color: var(--ok); font-weight: 700; }
/*  THEY STRADDLE THE EDGE RATHER THAN FLOAT OVER THE CONTENTS.

    Two earlier positions both failed the same way: inside the frame there is
    nowhere for a label to sit that is not already carrying something. Over the
    top corner it cut the mockup's own title in half; moved down, it covered a
    figure instead. A badge that hides data makes the frame look broken, which
    is the opposite of what a badge on a product shot is for.

    Sitting half outside the boundary, the only thing either one covers is the
    frame's own border - and hanging off the edge is what made them read as
    layered in the first place.

    On opposite diagonals, and the top one is on the RIGHT: the mockup's title
    and its three dots are both left-aligned in the chrome, so the left corner
    is the one part of that bar already carrying something.  */
.lp-chip.is-tl { top: -.9rem; right: 1.75rem; }
.lp-chip.is-br { bottom: -.9rem; left: 1.75rem; }

/* ══════════════════════════════════════════════════ the benefits strip ══ */
/*  The ten highlights, as one band the eye crosses in a second. As tiles they
    were ten boxes mostly full of air.  */

.lp-strip {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    padding: 1.1rem clamp(1rem, 3vw, 1.75rem);
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}

.lp-pill {
    display: inline-flex; align-items: center; gap: .45rem;
    font-size: .8125rem;
    color: var(--ink-soft);
    padding: .35rem .5rem;
}
.lp-pill .tick { color: var(--ok); font-weight: 700; }

/* ═════════════════════════════════════════════════════════ the cards ══ */
/*  ONE CARD, USED BY FOUR SECTIONS. Features, modules, schemes and the trust
    points are all "icon, name, one line", and giving each its own card was
    what made four consecutive sections look like four different websites.  */

.lp-grid { display: grid; gap: .85rem; }
.lp-grid.is-3 { grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr)); }
.lp-grid.is-4 { grid-template-columns: repeat(auto-fit, minmax(min(13.5rem, 100%), 1fr)); }

.lp-card {
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--lp-card-pad);
    transition: border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}

/*  A BORDER AND A SHADOW, AND NOTHING MOVES. This used to lift the card 2px.
    Two pixels is small, but a grid of sixteen of them rippling as the pointer
    crossed made the page look unsteady under the mouse - so the state is
    carried entirely by paint now. The border takes the accent because on a
    dark palette a shadow alone is nearly invisible, and the border is what
    makes the state readable there.  */
.lp-card:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow);
}

.lp-card-top { display: flex; align-items: center; gap: .65rem; margin-bottom: .5rem; }

/* ── the icon tile ───────────────────────────────────────────────────────
   THE ICON SITS ON A TINTED SQUARE rather than floating loose in the card.

   A bare 20px stroke icon beside 15px text has almost no visual weight - it
   reads as a bullet, not as a symbol, and a grid of them looks like a list
   that lost its indent. On its own ground the icon becomes an object: it
   anchors the corner of the card, gives the name something to sit against,
   and is the one place the accent appears at card level.

   Sized in rem so it follows the fluid type scale rather than fighting it. */
.lp-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    flex: none;

    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);

    transition: background var(--dur) var(--ease),
                color var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}

.lp-ico .ico { width: 1.15rem; height: 1.15rem; }

/*  The compact feature cards take a smaller tile - a 36px square inside a
    70px box leaves the name nowhere to go.  */
.lp-feat .lp-ico { width: 2rem; height: 2rem; border-radius: .3rem; }
.lp-feat .lp-ico .ico { width: 1rem; height: 1rem; }

/*  ON HOVER THE TILE FILLS, and that is the whole effect - one element
    changing colour rather than the whole card flashing. It used to grow 1.06
    with it; at this size that read as the icon jumping every time the pointer
    passed, which is the same unsteadiness the card lift was causing.  */
.lp-card:hover .lp-ico,
.lp-feat:hover .lp-ico {
    background: var(--accent);
    color: var(--accent-ink);
}

/*  An open feature card keeps the filled tile, so the card that is showing
    its detail is obvious even after the pointer has moved away.  */
.lp-feat[aria-expanded="true"] .lp-ico {
    background: var(--accent);
    color: var(--accent-ink);
}
.lp-card h3 { margin: 0; font-size: var(--fs-base); letter-spacing: -.008em; }
/*  PROSE IS SET AT READING SIZE, NOT AT CAPTION SIZE. This and the rules
    marked below were all on --fs-sm, which the scale reserves for captions and
    meta - 13.8px on a laptop. They are not captions; they are the sentences
    the page is actually asking somebody to read, and at that size the whole
    page felt shrunk even though its headings were 42px.  */
.lp-card p { margin: 0; font-size: var(--fs-base); line-height: 1.55; color: var(--ink-soft); }

/* ── the feature cards ───────────────────────────────────────────────────
   A small box carrying the name. Press it and the detail drops out beneath,
   so the two together read as one taller card.

   NO RESERVED HEIGHT, and that is the fix for the last round. The card used
   to keep a min-height big enough for the description it was hiding, so a
   one-line name sat in a box built for three lines and every tile looked
   half empty. The box is now exactly its own padding plus its own name.

   THE DETAIL IS POSITIONED, NOT IN THE FLOW. It hangs off the bottom of the
   card, so opening one cannot change the height of its grid row: nothing
   below moves, and the three cards beside it do not stretch to match. A card
   that grew in the flow would push the whole page down on every press.

   The seam is closed by pulling the panel up over the card's bottom border
   and squaring the two edges that meet, so an open card looks like one box
   rather than a tile with a tooltip under it.

   PRESS, NOT HOVER. A press is deliberate, works the same on a phone as on a
   mouse, and does not fire while somebody is only moving across the grid.  */

.lp-feat {
    position: relative;
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.lp-feat-in {
    display: flex;
    align-items: center;
    gap: .6rem;
    min-height: 100%;

    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: .85rem 1rem;

    transition: border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}

/*  THE HOVER STATE NO LONGER MOVES ANYTHING, and that was a real fault rather
    than a taste: crossing a grid of sixteen cards with the pointer lifted each
    one in turn, and the page appeared to twitch under the mouse. The border
    taking the accent and the shadow arriving say "this one" perfectly well
    while every card stays exactly where it was drawn.

    Nothing here cancels itself on an open card any more either - there is no
    lift left to cancel.  */
.lp-feat:hover .lp-feat-in {
    border-color: var(--accent);
    box-shadow: var(--shadow);
}
.lp-feat:focus-visible { outline: none; }
.lp-feat:focus-visible .lp-feat-in { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/*  1.3 rather than the body's 1.55. A name is a label, not prose, and the
    looser leading was costing eight pixels a line on the cards that wrap.  */
.lp-feat-name {
    font-size: var(--fs-base);
    font-weight: 650;
    letter-spacing: -.008em;
    line-height: 1.3;
}

/*  The affordance. Without it a tile that opens looks exactly like a tile
    that does nothing, and nobody presses it.  */
.lp-feat-sign {
    position: relative;
    width: .7rem; height: .7rem;
    margin-left: auto;
    flex: none;
    color: var(--ink-soft);
}
.lp-feat-sign::before,
.lp-feat-sign::after {
    content: ""; position: absolute; background: currentColor; border-radius: 1px;
    transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.lp-feat-sign::before { top: 50%; left: 0; width: 100%; height: 1.5px; transform: translateY(-50%); }
.lp-feat-sign::after  { left: 50%; top: 0; height: 100%; width: 1.5px; transform: translateX(-50%); }

/*  The upright goes, so the plus becomes a minus.  */
.lp-feat[aria-expanded="true"] .lp-feat-sign::after { transform: translateX(-50%) rotate(90deg); opacity: 0; }

/*  IT FADES AND SLIDES; IT DOES NOT ANIMATE ITS HEIGHT, and that is forced
    rather than chosen.

    The usual trick for an animated open is grid-template-rows: 0fr -> 1fr,
    which sizes the row to its content and needs no magic number. It cannot
    work here: this panel is absolutely positioned, and an `fr` inside an
    absolutely positioned box resolves against the free space in its
    containing block, not against its own content. With top:100% that free
    space is zero, so the row came out 1px tall and the text was clipped to a
    hairline while the content underneath measured a correct 56px.

    A max-height guess is the other common answer and is worse: too small
    clips a long description, too large makes a short one crawl open and then
    jump. So the panel opens at its true height and the motion is carried by
    opacity and a 4px rise, which is quick enough to read as opening.

    visibility, not just opacity, so a closed panel is out of the tab order
    and cannot be clicked through.  */
.lp-feat-detail {
    position: absolute;
    top: 100%; left: 0; right: 0;
    margin-top: -1px;          /* sit on the card's bottom border, not under it */
    z-index: 5;

    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-4px);
    transition: opacity var(--dur) var(--ease),
                transform var(--dur) var(--ease),
                visibility 0s linear var(--dur);
}

.lp-feat[aria-expanded="true"] .lp-feat-detail {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: none;
    transition: opacity var(--dur) var(--ease),
                transform var(--dur) var(--ease),
                visibility 0s;
}

/*  A <span>, not a <p>: a button's content model is phrasing content, so a
    paragraph inside one is invalid - the same reason the name is not an <h3>.
    overflow:hidden keeps the text inside the rounded bottom corners.  */
.lp-feat-text {
    display: block;
    overflow: hidden;
    font-size: var(--fs-sm);
    line-height: 1.55;
    color: var(--ink-soft);
    background: var(--raised);
    border: 1px solid var(--line);
    border-top: 0;
    border-radius: 0 0 var(--radius) var(--radius);
}

/*  The padding lives on an inner block rather than on the collapsing element,
    where it would keep the card 1.7rem tall when shut.  */
.lp-feat-text > span { display: block; padding: 0 1rem .85rem; }

.lp-feat[aria-expanded="true"] .lp-feat-in {
    border-color: var(--accent);
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
}
.lp-feat[aria-expanded="true"] .lp-feat-text {
    border-color: var(--accent);
    box-shadow: var(--shadow);
}
.lp-feat[aria-expanded="true"] .lp-feat-sign { color: var(--accent); }

/*  The module number. Tabular, so ten of them line up down the grid.  */
.lp-no {
    font-family: var(--font-num);
    font-size: .6875rem;
    font-weight: 700;
    color: var(--accent);
    background: var(--accent-soft);
    border-radius: var(--radius-sm);
    padding: .1rem .35rem;
    flex: none;
}

/* ══════════════════════════════════════════════════════ the showcase ══ */

.lp-show {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
    gap: clamp(1.75rem, 1rem + 3vw, 3.5rem);
    align-items: center;
}

.lp-points { list-style: none; margin: 1.5rem 0 0; padding: 0; display: grid; gap: .9rem; }
.lp-points li { display: flex; gap: .7rem; align-items: flex-start; font-size: var(--fs-base); }
.lp-points .tick { color: var(--ok); font-weight: 700; flex: none; }
.lp-points b { display: block; }
.lp-points p { margin: .1rem 0 0; font-size: var(--fs-sm); color: var(--ink-soft); }   /* reading size, see .lp-card p */

/* ══════════════════════════════════════════════════════ the pricing ══ */

.lp-plans {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
    gap: 1.25rem;
    align-items: stretch;
}

/*  A COLUMN, NOT A GRID, and the difference is the button. Packed to the top,
    each card's call to action sat wherever its own feature list happened to
    end; three buttons on three different lines is exactly the sort of thing
    that reads as unfinished without anybody being able to say why.  */
.lp-plan {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: .6rem;
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--gap-lg);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.lp-plan:hover { border-color: var(--line-strong); box-shadow: var(--shadow); }

/*  THE RECOMMENDED PLAN IS RAISED, NOT RECOLOURED. A plan filled with the
    accent shouts at the two beside it and makes the others look switched off;
    a ring and a shadow say "this one" while leaving all three readable.  */
.lp-plan.on { border-color: var(--accent); box-shadow: var(--shadow-lg); }
.lp-plan.on:hover { border-color: var(--accent); box-shadow: var(--shadow-lg); }

.lp-flag {
    position: absolute;
    top: -.6rem; left: var(--gap-lg);
    background: var(--accent);
    color: var(--accent-ink);
    font-size: .625rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: .07em;
    padding: .15rem .5rem;
    border-radius: var(--radius-pill);
}

.lp-plan h3 { margin: 0; font-size: var(--fs-lg); }
.lp-plan-for { margin: 0; font-size: var(--fs-sm); color: var(--ink-soft); }
.lp-price { display: flex; align-items: baseline; gap: .35rem; margin-top: .3rem; }
.lp-price b { font-family: var(--font-num); font-size: var(--fs-2xl); font-weight: 600; letter-spacing: -.03em; }
.lp-price span { font-size: .8125rem; color: var(--ink-soft); }

.lp-includes { list-style: none; margin: .4rem 0 0; padding: 0; display: grid; gap: .4rem; }
.lp-includes li { position: relative; padding-left: 1.2rem; font-size: var(--fs-base); color: var(--ink-soft); }
.lp-includes li::before { content: "\2713"; position: absolute; left: 0; color: var(--ok); font-weight: 700; }

/*  Pushed to the foot of the card by the space above it, so all three calls
    to action sit on one line across the row whatever the lists do.  */
.lp-plan .btn { margin-top: auto; justify-content: center; }

/*  The unset-pricing notice. A quiet strip rather than the full-width red
    band it was: it is information, not a failure, and a page that opens with
    a red bar across it reads as broken.  */
.lp-note {
    display: flex; gap: .6rem; align-items: flex-start;
    max-width: var(--lp-measure);
    margin: 0 auto clamp(1.5rem, 1rem + 1vw, 2rem);
    padding: .75rem .9rem;
    background: var(--warn-soft);
    border: 1px solid var(--warn);
    border-radius: var(--radius);
    font-size: .8125rem;
    line-height: 1.55;
    color: var(--ink);
}
.lp-note .mark { color: var(--warn); font-weight: 700; flex: none; }

/* ══════════════════════════════════════════════ coming, company, theme ══ */

/*  What is coming, as a strip of small cards. It was the tallest section on
    the page and it is the least useful one to somebody deciding whether to
    sign up - it belongs on the page, not near the top of it.  */
/* ══════════════════════════════════════════════════════════ company ══ */
/*  The statement on the left, the record on the right. A company section that
    is one centred paragraph says nothing a visitor can check; a wall of
    labelled rows says nothing they wanted to read. Side by side, the sentence
    makes the claim and the card is the evidence for it.  */

.lp-company {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, .95fr);
    gap: clamp(1.75rem, 1rem + 3vw, 3.5rem);
    align-items: start;
}

/*  The company's own tagline, set the way a tagline is set: small, capitalised
    and opened right up. Wide tracking is what stops five short words reading
    as a label; it is the one place on this page letter-spacing goes past .1em.

    It is taken from the mark itself rather than written here - the words are
    on the logo.  */
/*  It closes the block now rather than sitting between the mark and the name,
    so the space goes above it instead of below. The rule gives it something
    to sit against - without one a widely tracked line at this size reads as
    a stray fragment of the paragraph above.  */
.lp-cotag {
    display: block;
    width: fit-content;
    font-size: .625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .2em;
    color: var(--ink-soft);
    margin-top: .35rem;
    padding-top: 1rem;
    border-top: 1px solid var(--line);
}

/*  THE COMPANY SITE, under its own name. Sized and coloured like the sentence
    above it rather than like a button: it is an aside for the reader who wants
    the maker, not the thing the section is asking for.

    The arrow leans out on hover, the way the other outbound links on this page
    do, so the new tab is announced before it opens.  */
.lp-cosite {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    margin-top: .85rem;
    font-size: .875rem;
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
}
.lp-cosite:hover, .lp-cosite:focus-visible { text-decoration: underline; }

/*  .arw is drawn by app.css only inside a .btn - two borders, no icon font -
    so a link that wants the same arrow has to draw its own. Same construction,
    same lean on hover, so the two read as one gesture across the page.  */
.lp-cosite .arw {
    width: .38rem; height: .38rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(-45deg);
    transition: transform var(--dur) var(--ease);
}
.lp-cosite:hover .arw { transform: rotate(-45deg) translate(2px, 2px); }

/*  Tighter tracking than the shared heading, because this one is a company
    name in title case - long words, lots of stems, and at this size the
    default spacing makes them drift apart.  */
.lp-company-say .lp-h {
    max-width: 22rem;
    letter-spacing: -.028em;
    line-height: 1.15;
}

.lp-company-say .lp-sub { max-width: 30rem; }

/*  THE COMPANY MARK, CROPPED TO THE ARTWORK.

    MEASURED, NOT GUESSED. keycode-technology.jpg is 880x798 and the file has
    two faults, both from having been captured off a screen:

        rows 790-797   a solid white bar across the left half - the edge of
                       whatever window it was grabbed from. Eight rows of 798
                       is under one per cent, but at any size it draws a pale
                       line under the mark on a near-black plate, which is
                       the first thing an eye lands on.
        rows 0-114     empty ground above the K: 14.6 per cent of the height
                       spent on nothing.

    ONLY THE K IS SHOWN. The file is the full lock-up - symbol, KEYCODE
    TECHNOLOGY wordmark and a tagline - and at any size that fits a page the
    wordmark under it is a grey smudge. The symbol alone is what works as a
    mark beside a heading, and the name is set in type immediately below it
    anyway, so nothing is lost by cropping to it.

    MEASURED, NOT EYEBALLED. Scanning the file for ink:

        rows 116..449   the K            (334 tall)
        rows 450..499   empty            (the 50-row gap that separates them)
        rows 500..      KEYCODE TECHNOLOGY, then the tagline
        cols 258..568   the K            (311 wide), centred on x=413

    The crop is a 398px square centred on the K at (413, 282), which leaves
    about 44px of ground either side of it and 32px above and below - even
    margins, so the symbol sits in the middle of its plate rather than
    floating in it. That square also carries the file's white bottom bar and
    its empty top away entirely.

        2.2111 = 880 / 398     scale that makes the crop fill the plate
        0.5377 = 214 / 398     the horizontal pull, putting col 214 at the left
        0.2111 =  84 / 398     the vertical pull, putting row 84 at the top

    Delete this block and set height: var(--cologo-h) the day the mark arrives
    as a clean SVG or a properly trimmed PNG.

    line-height: 0 on the wrapper, because an inline image otherwise sits on a
    text baseline and leaves descender space under it that reads as the plate
    being off-centre.  */
/*  A BLOCK, NOT AN INLINE-BLOCK, and width:fit-content so the plate is only
    as wide as the mark. As an inline-block it shared a line box with the
    eyebrow above it, and because the plate is far taller than that chip the
    shared baseline pushed the chip to the FOOT of the line - so "Company"
    rendered under the logo instead of over it.  */
/*  "Built by", as a label rather than as part of the heading. It is the
    smaller half of the statement, and putting it on its own line is what lets
    the company's own name start the line under it.  */
.lp-builtby {
    display: block;
    font-size: .6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .1em;
    color: var(--ink-soft);
    margin-bottom: .45rem;
}

/*  THE MARK SET AS A LETTER, AND SIZED FROM MEASUREMENTS RATHER THAN BY EYE.

    em units throughout, so the mark tracks the heading through every step of
    the fluid type scale without one breakpoint.

    Both numbers below were guessed first and both were wrong - 1.02em and
    -0.13em, which drew the K a third too large and hung it four pixels below
    the line. What they should be comes from two facts:

        0.7119   the cap height of a real K in this face, in em, from
                 TextMetrics.actualBoundingBoxAscent
        0.9514   the share of keycode-k.png that is ink - the file carries a
                 small transparent pad on every side from the crop
        0.0257   the share of the file that is pad BELOW the ink

    height       = (cap * 1.24) / 0.9514 = 0.928em
                   the 1.24 is deliberate. A drawn mark has no optical
                   overshoot of its own, so at exactly cap height it reads a
                   shade small; beyond that, this K is the company's mark and
                   is meant to carry the line rather than merely fill a slot
                   in it. It rises above the cap and drops just below the
                   baseline, which is how a logotype initial is normally set.

    vertical-align = -(0.928 * 0.0257) = -0.024em
                   an inline image seats its own BOTTOM EDGE on the baseline,
                   and that edge is the pad, not the ink - so the letter would
                   otherwise float. This drops it by exactly the pad.

    The negative right margin closes the pad on the right, so "K" and "eycode"
    set as one word rather than with a space wedged between them.

    Re-measure all three if the mark is ever re-exported.  */
.lp-coname { display: block; }

.lp-conameK {
    height: 0.928em;
    width: auto;
    vertical-align: -0.024em;
    margin-right: -0.035em;
}

.lp-company-card {
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: clamp(1.25rem, 1rem + 1.5vw, 1.75rem);
}

/*  A titled panel: the header sits on its own line with a rule under it, so
    the card reads as a document rather than as a stack of loose pairs. The
    rule is the card's own padding wide, which is what makes it look built in
    rather than drawn on.  */
.lp-company-card > h3 {
    font-size: .625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .09em;
    color: var(--ink-soft);
    margin: 0;
    padding-bottom: .85rem;
    border-bottom: 1px solid var(--line);
}

/*  The facts read as a record rather than as a form: the label small and
    quiet above, the value at full strength beneath it, each pair on its own
    row. Two columns of dt/dd side by side is how an invoice looks, and an
    invoice is not what builds confidence in who wrote the software.

    A hairline between rows rather than a gap. At this density plain spacing
    lets the eye pair a value with the WRONG label - the rule is what makes
    each pair a unit.  */
.lp-company-card .lp-facts {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
}
.lp-company-card .lp-facts dt {
    font-size: .625rem;
    letter-spacing: .08em;
    padding-top: .85rem;
    margin-top: .85rem;
    border-top: 1px solid var(--line);
}
.lp-company-card .lp-facts dd {
    font-size: var(--fs-base);
    font-weight: 600;
    color: var(--ink);
    margin-top: .15rem;
    line-height: 1.4;
}
.lp-company-card .lp-facts dd.unset { font-weight: 400; }

/*  The disclosure is the card's foot: flush to the card's own edges, so it
    reads as part of the panel rather than as a box dropped inside one.  */
.lp-company-card .lp-more {
    margin: 1.25rem calc(clamp(1.25rem, 1rem + 1.5vw, 1.75rem) * -1) calc(clamp(1.25rem, 1rem + 1.5vw, 1.75rem) * -1);
    border: 0;
    border-top: 1px solid var(--line);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    background: var(--ground);
}
.lp-company-card .lp-more > summary { padding-inline: clamp(1.25rem, 1rem + 1.5vw, 1.75rem); }
.lp-company-card .lp-more-body { padding: 0 clamp(1.25rem, 1rem + 1.5vw, 1.75rem) 1.25rem; }

/*  ── the two groups inside it ────────────────────────────────────────────
    Side by side, so twelve fields are six rows deep instead of twelve. They
    are also two different kinds of fact, so the split is one the reader
    already expects.  */
.lp-more-cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
    gap: 1.25rem 2rem;
}

.lp-more-cols h4 {
    font-size: .625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .09em;
    color: var(--ink-soft);
    margin: 0 0 .5rem;
}

/*  LABEL AND VALUE ON ONE LINE, which is what actually halves the height.
    Stacked, each pair costs two lines and a rule; inline, the label is a
    column and the value sits beside it.

    The label column is sized to its content rather than fixed, so "Account
    number" and "IFSC" line their values up against the same edge.  */
.lp-company-card .lp-facts.is-inline {
    grid-template-columns: auto minmax(0, 1fr);
    gap: .3rem .85rem;
    align-items: baseline;
}
.lp-company-card .lp-facts.is-inline dt {
    margin: 0;
    padding: 0;
    border-top: 0;
    white-space: nowrap;
}
.lp-company-card .lp-facts.is-inline dd {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: 500;
}
.lp-company-card .lp-facts.is-inline dd.unset { font-weight: 400; }

@media (max-width: 56rem) {
    .lp-company { grid-template-columns: minmax(0, 1fr); }
}

.lp-facts { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .5rem 1.25rem; margin: 0; }
.lp-facts dt { font-size: .6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); }
.lp-facts dd { margin: 0; font-size: var(--fs-base); }

/*  An unfinished detail is shown as visibly unfinished rather than as a blank
    line that reads like a design choice.  */
.lp-facts dd.unset { color: var(--ink-soft); font-style: italic; }

/*  SECONDARY BY CONSTRUCTION. Banking details are not a marketing section -
    they are a reference somebody looks up once, and they are also empty. A
    disclosure keeps the data on the page without giving it a third of a
    screen.  */
.lp-more { border: 1px solid var(--line); border-radius: var(--radius); margin-top: 1rem; }
.lp-more > summary {
    padding: .7rem .9rem;
    font-size: .8125rem; font-weight: 600;
    color: var(--ink-soft);
    cursor: pointer;
    list-style: none;
    display: flex; align-items: center; gap: .5rem;
}
.lp-more > summary::-webkit-details-marker { display: none; }
.lp-more > summary::marker { content: ""; }
.lp-more > summary:hover { color: var(--ink); }
.lp-more[open] > summary { border-bottom: 1px solid var(--line); }
.lp-more .chev {
    width: .35rem; height: .35rem;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-45deg);
    transition: transform var(--dur) var(--ease);
    margin-left: auto;
}
.lp-more[open] .chev { transform: rotate(45deg); }
.lp-more-body { padding: .9rem; }

/*  The theme band is the one place the accent tint carries a whole section,
    so the page has a single warm stop in it instead of an even wash - and it
    is the section that is actually about colour.  */
/*  The theme band that used to sit here is gone - the picker is in the top
    bar now, on every public screen, so the landing page no longer carries a
    section for it. Its one rule went with it.  */

/* ══════════════════════════════════════════════════════════ contact ══ */
/*  The pitch on the left, the form on the right. A form alone in the middle
    of a marketing page is a demand; beside the reason for filling it in, it
    is an answer.  */

.lp-contact {
    display: grid;
    grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr);
    gap: clamp(1.75rem, 1rem + 3vw, 3.5rem);
    align-items: start;

    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: clamp(1.5rem, 1rem + 2vw, 2.75rem);
}

.lp-contact-say .lp-h,
.lp-contact-say .lp-sub { max-width: 26rem; }

.lp-contact-facts { margin-top: 1.5rem; }

/*  THE FORM KEEPS app.css's .fields AND .field MARKUP and re-skins it, rather
    than inventing a second set of controls. A fix to focus, contrast or
    readonly styling then lands in one place and reaches both the till and
    this page - which is the whole argument for a component.

    WHAT IS RE-SKINNED IS SIZE. Those controls are drawn for a purchase bill:
    forty of them on one screen, where every millimetre is one the operator
    scrolls for. A stranger filling in five fields once is the opposite case
    and wants room.  */

/*  TWO COLUMNS THAT ACTUALLY PAIR UP. auto-fit was putting three narrow
    fields on one row at some widths and one on the next, which is what makes
    a form look assembled rather than designed. Name sits beside phone,
    email beside shop, and the message spans both.  */
.lp-form .fields {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .8rem .9rem;
}

@media (max-width: 34rem) {
    .lp-form .fields { grid-template-columns: minmax(0, 1fr); }
}

/*  Full ink on the label, not the muted grey the dense forms use. On a till
    the label is a reminder of a field the operator already knows; here it is
    an instruction to somebody seeing it for the first time.  */
.lp-form .field > label {
    display: flex;
    align-items: baseline;
    gap: .4rem;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--ink);
    margin-bottom: .1rem;
}

/*  WHICH FIELDS ARE OPTIONAL IS MARKED, NOT WHICH ARE REQUIRED. Three
    asterisks and two blanks reads as a form that mostly demands things; two
    quiet "optional" tags read as a form that mostly does not. Same
    information, and the second one gets finished more often.  */
.lp-form .opt {
    font-size: .625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--ink-soft);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    padding: .05rem .4rem;
}

/*  40px, not 46px. The taller field was drawn for a form that is the whole
    screen; this one shares a card with the reason for filling it in, and at
    46px the five fields plus the message made the contact section the tallest
    band on the page - taller than the pricing it is meant to follow.

    Still comfortably above the 24px of a till row: this is a stranger typing
    once, not an operator keying forty.  */
.lp-form input,
.lp-form textarea {
    min-height: 2.5rem;
    padding: .5rem .75rem;
    /*  What somebody is typing is the last thing on the page that should be
        set at caption size.  */
    font-size: var(--fs-base);
    background: var(--ground);         /* sunk into the card, not floating on it */
    border-color: var(--line);
}

.lp-form textarea {
    min-height: 4.75rem;
    line-height: 1.5;
    resize: vertical;                  /* vertical only - sideways breaks the grid */
}

/*  --ink-soft, NOT --ink-faint. Faint measures 4.25:1 on the sunken field in
    night mode, under the 4.5:1 floor - and a placeholder is text like any
    other. Soft clears it at 6.5:1 and is still plainly lighter than what gets
    typed in (13.5:1), so a filled field never reads as an empty one.  */
.lp-form input::placeholder,
.lp-form textarea::placeholder { color: var(--ink-soft); }

.lp-form input:hover,
.lp-form textarea:hover { border-color: var(--line-strong); }

.lp-form input:focus,
.lp-form textarea:focus {
    background: var(--raised);
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/*  :user-invalid, NOT :invalid.

    :invalid matches from the moment the page loads, so an empty required
    field is painted red before the person has typed a single character -
    which is the browser telling somebody off for not having started yet.
    :user-invalid waits until they have actually interacted with it.

    Unsupported browsers simply never match it and get no red at all, which is
    the right way for this to degrade.  */
.lp-form input:user-invalid,
.lp-form textarea:user-invalid { border-color: var(--bad); }

.lp-form input:user-invalid:focus,
.lp-form textarea:user-invalid:focus { box-shadow: 0 0 0 3px var(--bad-soft); }

/*  The button and the line that sets expectations, separated from the fields
    by a rule so the form has a clear end.  */
.lp-form-foot {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
    margin-top: 1rem;
    padding-top: .9rem;
    border-top: 1px solid var(--line);
}

.lp-form-note {
    font-size: var(--fs-sm);
    color: var(--ink-soft);
    margin: 0;
}

/*  Full width on a phone, where a button that does not fill the row looks
    like it is missing something.  */
.lp-form-foot .btn { width: 100%; }

@media (min-width: 34rem) {
    .lp-form-foot .btn { width: auto; }
}

/* ══════════════════════════════════════════════════════════════ FAQ ══ */

.lp-faq { max-width: var(--lp-measure); margin-inline: auto; display: grid; gap: .6rem; }

.lp-faq details {
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    transition: border-color var(--dur) var(--ease);
}
.lp-faq details[open] { border-color: var(--line-strong); }

.lp-faq summary {
    display: flex; align-items: center; gap: .75rem;
    padding: .9rem 1rem;
    font-weight: 600;
    font-size: var(--fs-base);
    cursor: pointer;
    list-style: none;
}
.lp-faq summary::-webkit-details-marker { display: none; }
.lp-faq summary::marker { content: ""; }

/*  A plus that becomes a minus, drawn from two borders. It says "this opens"
    without an icon font, and the state is visible before the click.  */
.lp-faq .sign { position: relative; width: .75rem; height: .75rem; margin-left: auto; flex: none; }
.lp-faq .sign::before,
.lp-faq .sign::after {
    content: ""; position: absolute; background: currentColor; border-radius: 1px;
    transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.lp-faq .sign::before { top: 50%; left: 0; width: 100%; height: 1.5px; transform: translateY(-50%); }
.lp-faq .sign::after  { left: 50%; top: 0; height: 100%; width: 1.5px; transform: translateX(-50%); }
.lp-faq details[open] .sign::after { transform: translateX(-50%) rotate(90deg); opacity: 0; }

.lp-faq details p { margin: 0; padding: 0 1rem 1rem; font-size: var(--fs-base); line-height: 1.65; color: var(--ink-soft); }

/* ══════════════════════════════════════════════════════ the final ask ══ */

.lp-final {
    text-align: center;
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: clamp(1.5rem, 1.2rem + 1.5vw, 2.25rem) clamp(1.25rem, 1rem + 2vw, 2.5rem);
    box-shadow: var(--shadow);
}
.lp-final h2 { font-size: var(--fs-2xl); letter-spacing: -.025em; margin: 0 0 .6rem; }
.lp-final p { max-width: 34rem; margin: 0 auto 1.1rem; color: var(--ink-soft); }
.lp-final .lp-cta { justify-content: center; }

/* ══════════════════════════════════════════════════════════ utilities ══ */

/*  Said to a screen reader and to nobody else. Used for the caption on the
    product mockup, which is aria-hidden.  */
.lp-sr {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; border: 0;
}

/*  ── the one animation on the page ────────────────────────────────────────
    A short rise as a section arrives. Applied by site.js only after it has
    confirmed IntersectionObserver exists, so the content is NEVER hidden
    waiting for script that may not run - the class starts absent and the page
    is fully readable without it.  */
/*  THE .lp-anim GATE IS THE WHOLE POINT. Without it this rule would hide
    every section for anybody whose script did not run - no JavaScript, an old
    browser, a blocked file - and they would be left with a page of headings
    and nothing under them.

    site.js adds .lp-anim to <html> only AFTER confirming IntersectionObserver
    exists, so the hiding and the revealing arrive together or not at all.  */
html.lp-anim .lp-rise { opacity: 0; }

html.lp-anim .lp-rise.is-in {
    opacity: 1;
    transition: opacity var(--dur-slow) var(--ease);
}

@media (prefers-reduced-motion: reduce) {
    html.lp-anim .lp-rise,
    html.lp-anim .lp-rise.is-in { opacity: 1; transition: none; }
}

/* ═══════════════════════════════════════════════════ smaller screens ══ */
/*  All of it here rather than scattered through the file, so what a phone
    gets can be read in one place.  */

/*  The hero's second column goes under the first before the text has to
    squeeze - a 30-character measure beside a mockup is worse than either one
    at full width.  */
@media (max-width: 62rem) {
    .lp-hero { grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }
    .lp-show { grid-template-columns: minmax(0, 1fr); }
    .lp-show .lp-shot { order: 2; }
}

@media (max-width: 48rem) {
    /*  Pulled in from the corners so a narrow screen cannot push them past the
        edge of the page, but still on the boundary rather than over the data.
        Same sides as above - is-tl is anchored right, is-br left.  */
    .lp-chip.is-tl { right: .9rem; }
    .lp-chip.is-br { left: .9rem; }
}

@media (max-width: 40rem) {
    .lp { --lp-section: 1rem; }

    /*  The full row of anchors is replaced by the disclosure. Both are in the
        markup and each is hidden at the width where it is wrong, so neither
        depends on script.  */
    .lp-nav-in > a { display: none; }
    .lp-nav-more { display: block; }

    .lp-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .lp-stats .lp-stat:last-child { grid-column: 1 / -1; }

    .lp-cta .btn { flex: 1 1 100%; }
}

/*  The contact pitch and its form stack before either has to squeeze - a
    26rem argument beside a two-column form is worse than both at full width. */
@media (max-width: 56rem) {
    .lp-contact { grid-template-columns: minmax(0, 1fr); }
}

/* ═══════════════════════════════════ a light passes over the buttons ══ */
/*  The same sweep the logo has, on every button of this page: a bright band
    crossing the face every few seconds, faster under the pointer. On the
    filled buttons the light is white over the accent; on the outlined ones
    it is the accent itself, faint, so it never washes the text out. The
    layer is under the label and off for
    anyone who asked for less motion.  */
@keyframes btn-shine {
    0%   { transform: translateX(-140%) skewX(-18deg); }
    55%  { transform: translateX(240%) skewX(-18deg); }
    100% { transform: translateX(240%) skewX(-18deg); }
}
.lp .btn { position: relative; overflow: hidden; isolation: isolate; }
.lp .btn::before {
    content: "";
    position: absolute;
    top: -20%; bottom: -20%;
    left: 0; width: 45%;
    background: linear-gradient(105deg,
        transparent 0%,
        color-mix(in srgb, var(--accent) 22%, transparent) 30%,
        color-mix(in srgb, var(--accent) 38%, transparent) 50%,
        color-mix(in srgb, var(--accent) 22%, transparent) 70%,
        transparent 100%);
    pointer-events: none;
    /*  Below the label, above the button's own face: the button is its own
        stacking context, so -1 lands exactly there.  */
    z-index: -1;
    animation: btn-shine 3.4s ease-in-out infinite;
}
.lp .btn-primary::before {
    background: linear-gradient(105deg,
        transparent 0%,
        rgb(255 255 255 / .35) 30%,
        rgb(255 255 255 / .7) 50%,
        rgb(255 255 255 / .35) 70%,
        transparent 100%);
}
.lp .btn:hover::before { animation-duration: 1.2s; }
@media (prefers-reduced-motion: reduce) {
    .lp .btn::before { animation: none; display: none; }
}

/* ═══════════════════════════════════ the theme control, in this row ══ */
/*  The same pill the bar drew, sized to the nav's own anchors and pushed to
    the row's end; its panel opens under it, anchored right so it never runs
    off the screen. On a phone the row is the "Sections" disclosure and the
    theme pill sits beside it.  */
.lp-nav .themebox { position: relative; flex: none; }
.lp-nav .themebox > summary { min-height: 2.1rem; padding: .25rem .8rem .25rem .35rem; }
.lp-nav .themebox > summary .ico { width: 1.5rem; height: 1.5rem; }
.lp-nav .themebox-pop { right: 0; left: auto; }

/* ═════════════════════════════════════════════════ the Android app ══
   Four real app screens in phone-shaped frames. A row on a wide screen;
   on a phone, a sideways-swiping strip that snaps one screen at a time,
   so nothing is squeezed to an unreadable width.  */
.lp-phones {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: clamp(1rem, 2.5vw, 2rem);
    max-width: 68rem;
    margin: 2rem auto 0;
    padding: 0 1rem;
}

.lp-phone {
    margin: 0;
    text-align: center;
}

.lp-phone img {
    display: block;
    width: 100%;
    max-width: 16rem;
    height: auto;
    margin: 0 auto;
    border-radius: 1.6rem;
    border: .45rem solid #111827;
    background: #111827;
    box-shadow: 0 1.2rem 2.5rem rgba(15, 23, 42, .18), 0 .2rem .6rem rgba(15, 23, 42, .12);
}

.lp-phone figcaption {
    margin-top: .9rem;
    font-weight: 600;
    color: var(--ink);
    font-size: var(--fs-base);
}

.lp-app-note {
    text-align: center;
    margin: 1.6rem 0 0;
    color: var(--ink-soft);
    font-size: var(--fs-sm);
}

@media (max-width: 760px) {
    .lp-phones {
        display: flex;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        gap: 1rem;
        padding: .5rem 1rem 1rem;
        -webkit-overflow-scrolling: touch;
    }

    .lp-phone {
        flex: 0 0 70%;
        scroll-snap-align: center;
    }
}
