/*
    Placed Memories — the public site
    =================================

    The art direction is the one the brand already had, taken off the wordmark:
    a scrapbook. Thick navy outlines, hard offset shadows (never blurs), flat
    fills, and everything rotated a degree or two as though it had been stuck
    down by hand. Nothing here is a glass panel or a gradient mesh; those belong
    to a different product.

    Hand-written and served from `public/`, with no build step. The reasoning is
    the same as it was for the holding page this replaces: the marketing site
    must render correctly from a bare PHP server, and a landing page that
    arrives unstyled because an asset manifest went missing is a worse failure
    than a long stylesheet.

    Light in both colour schemes, deliberately. The wordmark's "Placed" is the
    brand navy, which all but vanishes on a dark ground — which is also why the
    app's splash screen dropped its dark variant. The mark is drawn for paper,
    so the page gives it paper.
*/

:root {
    color-scheme: light;

    --paper: #F8F9FB;
    --paper-2: #EDF3FA;
    --paper-3: #FFF6E8;
    --card: #FFFFFF;

    --outline: #1F2B57;
    --ink: #1F2B57;
    --ink-soft: #45507C;
    --ink-mute: #5A6693;

    --pink: #FB4E8E;
    --pink-soft: #FFE3EE;
    --coral: #FF6B7D;
    --sky: #7FC9F0;
    --sky-deep: #2E93CC;
    --sky-soft: #E1F2FC;
    --grass: #5FBE7E;
    --grass-soft: #E1F6E7;
    --sun: #FFD36E;
    --sun-soft: #FFF3D8;
    --tape: #FFC6DA;
    --violet: #A68BFF;
    --violet-soft: #EFE9FF;

    --drop: 0 5px 0 var(--outline);
    --drop-sm: 0 3px 0 var(--outline);
    --drop-lg: 0 8px 0 var(--outline);

    --display: 'Fredoka', ui-rounded, 'Segoe UI', system-ui, sans-serif;
    --body: 'Nunito', ui-rounded, 'Segoe UI', system-ui, sans-serif;

    --t-xs: .8125rem;
    --t-sm: .9375rem;
    --t-base: 1.0625rem;
    --t-md: 1.1875rem;
    --t-lg: 1.5rem;
    --t-xl: clamp(1.85rem, 3.6vw, 2.6rem);
    --t-2xl: clamp(2.35rem, 6.4vw, 4.5rem);

    --gutter: clamp(1.15rem, 4.5vw, 3.5rem);
    --bar: 66px;
}

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

html { scroll-behavior: smooth; scroll-padding-top: calc(var(--bar) + 16px); }

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--body);
    font-size: var(--t-base);
    line-height: 1.62;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

h1, h2, h3, h4 {
    font-family: var(--display);
    margin: 0;
    line-height: 1.06;
    letter-spacing: -.018em;
    text-wrap: balance;
    font-weight: 600;
}
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; }
button { font: inherit; }

:focus-visible { outline: 3px solid var(--pink); outline-offset: 3px; border-radius: 8px; }

.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.wrap { width: 100%; max-width: 76rem; margin-inline: auto; padding-inline: var(--gutter); }
.wrap--narrow { max-width: 48rem; }

.band { padding-block: clamp(3.5rem, 8vw, 6.5rem); position: relative; }
.band--alt { background: var(--paper-2); }
.band--warm { background: var(--paper-3); }
.band--tight { padding-block: clamp(2.5rem, 5vw, 4rem); }

/* --------------------------------------------------------------- pieces --- */

.eyebrow {
    display: inline-block;
    font-family: var(--display);
    font-weight: 600;
    font-size: var(--t-xs);
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--outline);
    background: var(--sun);
    border: 2.5px solid var(--outline);
    border-radius: 999px;
    padding: .3rem .85rem;
    box-shadow: var(--drop-sm);
    transform: rotate(-1.6deg);
    margin-bottom: 1.3rem;
}
.eyebrow--pink { background: var(--tape); }
.eyebrow--sky { background: var(--sky); }
.eyebrow--grass { background: var(--grass); }

.lede { font-size: var(--t-md); color: var(--ink-soft); max-width: 36rem; line-height: 1.6; }
.lede--center { margin-inline: auto; text-align: center; }

.hl { color: var(--pink); position: relative; white-space: nowrap; }
.hl::after {
    content: "";
    position: absolute;
    left: -2%; right: -2%; bottom: -.06em;
    height: .14em;
    background: var(--sun);
    border-radius: 999px;
    transform: rotate(-.8deg);
    z-index: -1;
}

.sticker {
    background: var(--card);
    border: 3px solid var(--outline);
    border-radius: 20px;
    box-shadow: var(--drop);
}

.tilt-a { transform: rotate(-2.4deg); }
.tilt-b { transform: rotate(1.8deg); }
.tilt-c { transform: rotate(-1.2deg); }
.tilt-d { transform: rotate(2.6deg); }

/* -------------------------------------------------------------- buttons --- */

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
    font-family: var(--display);
    font-weight: 600;
    font-size: var(--t-base);
    /* 44pt minimum target — the app's own accessibility floor. */
    min-height: 48px;
    padding: .6rem 1.5rem;
    border-radius: 999px;
    border: 3px solid var(--outline);
    background: var(--pink);
    color: #FFF;
    cursor: pointer;
    box-shadow: var(--drop);
    transition: transform .12s ease, box-shadow .12s ease;
}
.btn:hover { transform: translateY(-2px); box-shadow: var(--drop-lg); }
.btn:active { transform: translateY(4px); box-shadow: 0 1px 0 var(--outline); }
.btn[disabled] { opacity: .6; cursor: default; transform: none; box-shadow: var(--drop-sm); }

.btn--sun { background: var(--sun); color: var(--outline); }
.btn--white { background: var(--card); color: var(--ink); }
.btn--sky { background: var(--sky); color: var(--outline); }
.btn--sm { min-height: 40px; font-size: var(--t-sm); padding: .4rem 1.1rem; border-width: 2.5px; }

.btn__store { display: grid; text-align: left; line-height: 1.15; }
.btn__store small { font-size: .68rem; font-weight: 500; opacity: .85; }

/*
    The app is not released, so there is no store button unless somebody has
    put a real URL in the CRM. A control that looks real and opens nothing is
    worse than an empty state — this says the true thing instead.
*/
.soon {
    display: inline-flex; align-items: center; gap: .5rem;
    font-family: var(--display);
    font-weight: 600;
    font-size: var(--t-sm);
    color: var(--outline);
    background: var(--card);
    border: 2.5px dashed var(--outline);
    border-radius: 999px;
    padding: .55rem 1.1rem;
}

/* ------------------------------------------------------------ top of page - */

.announce {
    position: relative; z-index: 60;
    background: var(--outline);
    color: #FFF;
    font-size: var(--t-sm);
    font-weight: 600;
}
.announce__inner {
    display: flex; align-items: center; justify-content: center; gap: .75rem;
    flex-wrap: wrap;
    padding: .55rem var(--gutter);
    text-align: center;
}
.announce a { text-decoration: underline; text-underline-offset: 3px; }

.nav {
    position: sticky; top: 0; z-index: 50;
    transition: background .2s ease, box-shadow .2s ease, backdrop-filter .2s ease;
}
.nav[data-stuck="true"] {
    background: rgba(248, 249, 251, .82);
    backdrop-filter: blur(12px);
    box-shadow: 0 1px 0 rgba(31, 43, 87, .12);
}
.nav__inner {
    display: flex; align-items: center; gap: 1rem;
    min-height: var(--bar);
    padding-inline: var(--gutter);
    max-width: 76rem; margin-inline: auto;
}
.nav__logo {
    display: flex; align-items: center; gap: .55rem;
    font-family: var(--display); font-weight: 600; font-size: 1.0625rem;
    /* The wordmark is two words and must stay on one line; wrapped, it doubles
       the height of the bar and shunts the burger off centre. */
    white-space: nowrap;
}
.nav__logo img { width: 32px; height: 32px; display: block; border-radius: 9px; }
.nav__links { display: flex; align-items: center; gap: .25rem; margin-left: auto; }
.nav__link {
    padding: .5rem .8rem;
    border-radius: 999px;
    font-family: var(--display); font-weight: 500; font-size: var(--t-sm);
    color: var(--ink-soft);
}
.nav__link:hover { background: var(--card); color: var(--ink); }
.nav__cta { margin-left: .5rem; }

.nav__burger {
    display: none;
    margin-left: auto;
    width: 46px; height: 46px;
    align-items: center; justify-content: center;
    border-radius: 999px;
    border: 2.5px solid var(--outline);
    background: var(--card);
    box-shadow: var(--drop-sm);
    cursor: pointer;
}

.nav__sheet {
    position: fixed; inset: 0; z-index: 55;
    background: var(--paper);
    padding: calc(var(--bar) + 1rem) var(--gutter) 2rem;
    display: grid; align-content: start; gap: .25rem;
}
.nav__sheet[hidden] { display: none; }
.nav__sheet a {
    padding: .9rem .4rem;
    font-family: var(--display); font-weight: 500; font-size: 1.375rem;
    border-bottom: 2px solid rgba(31, 43, 87, .08);
}
.nav__sheet .btn { margin-top: 1.25rem; }
.nav__close {
    position: absolute; top: 12px; right: var(--gutter);
    width: 46px; height: 46px;
    display: grid; place-items: center;
    border-radius: 999px;
    border: 2.5px solid var(--outline);
    background: var(--card);
    cursor: pointer;
}

/* ----------------------------------------------------------------- hero --- */

.hero { padding-block: clamp(2rem, 5vw, 3.5rem) clamp(3.5rem, 8vw, 6rem); }

.hero__inner {
    display: grid;
    grid-template-columns: minmax(0, 1.02fr) minmax(0, .98fr);
    gap: clamp(2.5rem, 6vw, 4rem);
    align-items: center;
}

.hero h1 { font-size: var(--t-2xl); max-width: 13ch; }
.hero__lede { margin-top: 1.4rem; }
.hero__cta { margin-top: 2rem; display: flex; flex-wrap: wrap; gap: .8rem; align-items: center; }
.hero__scroll {
    display: inline-flex; align-items: center; gap: .4rem;
    margin-top: 1.6rem;
    font-family: var(--display); font-weight: 500; font-size: var(--t-sm);
    color: var(--ink-mute);
}
.hero__scroll:hover { color: var(--pink); }
.hero__scroll svg { animation: nudge 2.2s ease-in-out infinite; }
@keyframes nudge { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(4px); } }

.hero__stage { position: relative; display: grid; place-items: center; min-height: 34rem; }

/*
    The floating memory cards.

    They are positioned as percentages of the stage rather than pinned to the
    phone, so the composition survives every width without the cards drifting
    over the screen they are supposed to be pointing at. Each one drifts on its
    own cycle — a single shared animation makes six cards read as one moving
    object.
*/
.memcard {
    position: absolute;
    width: min(12.5rem, 48%);
    padding: .7rem .8rem .75rem;
    background: var(--card);
    border: 2.5px solid var(--outline);
    border-radius: 14px;
    box-shadow: var(--drop-sm);
    font-size: var(--t-xs);
    line-height: 1.45;
    /*
        In *front* of the phone, not behind it.

        Behind, the phone's opaque body cuts a card off mid-word, which reads as
        a rendering bug rather than as depth — the column is never wide enough
        for a 200px card to clear a 280px phone. In front, and overlapping only
        the phone's outer edge, the same overlap reads as layering.
    */
    z-index: 5;
    animation: drift 7s ease-in-out infinite;
}
.memcard__place {
    display: flex; align-items: center; gap: .3rem;
    font-family: var(--display); font-weight: 600; font-size: .8125rem;
    color: var(--outline);
    margin-bottom: .2rem;
}
.memcard__note { color: var(--ink-soft); }
.memcard__thumb {
    width: 100%; height: 10rem;
    border: 2px solid var(--outline); border-radius: 8px;
    margin-bottom: .45rem;
    object-fit: cover; display: block;
}

/*
    Positioned so each card clips only the phone's outer edge — a dozen pixels,
    enough to read as layered, not enough to cover the map or the preview sheet
    the screen is there to show.
*/
.memcard--1 { top: 0;     left: -12%;  animation-delay: 0s;    transform: rotate(-4deg); }
/*
    The left-hand cards overhang into the text column, which is inside the page
    and always safe. This one overhangs past the *wrap*, so its overhang is
    bounded by the gutter — the only space there is between the wrap and the
    edge of the screen.

    A flat `-12%` overflowed the document between 992px and roughly 1330px,
    where the wrap is viewport-bound and has no slack either side: 12% of the
    stage is a bigger number than the gutter there, so the card hung off the
    edge and the page scrolled sideways. Taking the smaller of the two, with a
    little of the gutter to spare, is true at every width.
*/
.memcard--2 { top: 42%;   right: calc(-1 * min(12%, var(--gutter) * .7)); animation-delay: -2.3s; transform: rotate(3deg); }
.memcard--3 { bottom: -10%; left: -10%;  animation-delay: -4.6s; transform: rotate(2deg); }

@keyframes drift {
    0%, 100% { translate: 0 0; }
    50%      { translate: 0 -11px; }
}

/* The thread from a card to the pin it belongs to. */
.threads { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.threads path {
    fill: none;
    stroke: var(--outline);
    stroke-width: 2;
    stroke-dasharray: 2 7;
    stroke-linecap: round;
    opacity: .4;
}

/* ---------------------------------------------------------------- phone --- */

/*
    A real device, not a sticker.

    The one object on the page that keeps its own materials: everything around
    it is flat fills, navy outlines and hard offset shadows, so a photoreal
    phone reads as an actual thing set down on a scrapbook page rather than
    another drawing. That contrast is the point — do not "harmonise" it back
    into the sticker style.

    Proportions are the real ones (149.6 x 71.5 mm body) and every radius is a
    percentage *pair*, so the corners stay circular however wide the phone is
    rendered. A single percentage goes elliptical on a box this tall.
*/
.phone { position: relative; width: min(16rem, 68%); z-index: 4; }

.phone__body {
    position: relative;
    aspect-ratio: 71.5 / 149.6;
    padding: 1.6%;
    border-radius: 14.5% / 6.93%;
    background: linear-gradient(145deg,
        #E9E9EE 0%, #9A9AA4 11%, #F3F3F6 29%,
        #85858E 51%, #EDEDF1 71%, #9296A0 87%, #DEDEE3 100%);
    box-shadow:
        inset 0 1px 1px rgba(255, 255, 255, .55),
        0 26px 50px -18px rgba(15, 22, 40, .55),
        0 10px 22px -12px rgba(15, 22, 40, .40);
}

.phone__glass { position: relative; height: 100%; padding: 1.9%; border-radius: 13.1% / 6.26%; background: #07070A; }

.phone__screen {
    position: relative; height: 100%;
    display: flex; flex-direction: column;
    background: var(--card);
    border-radius: 11.8% / 5.6%;
    overflow: hidden;
}

.phone__island {
    position: absolute; top: 3%; left: 50%;
    transform: translateX(-50%);
    width: 30%; aspect-ratio: 125 / 36;
    border-radius: 999px; background: #000; z-index: 5;
}

.phone__glare {
    position: absolute; inset: 0; border-radius: inherit;
    background: linear-gradient(118deg,
        rgba(255, 255, 255, .15) 0 15%,
        rgba(255, 255, 255, .04) 21%,
        transparent 33%);
    pointer-events: none; z-index: 6;
}

.phone__btn { position: absolute; z-index: -1; background: linear-gradient(180deg, #CBCBD3, #8C8C95 42%, #B7B7C0); }
.phone__btn--action { left: -1.4%; top: 15.5%; width: 1.5%; height: 4.6%; border-radius: 2px 0 0 2px; }
.phone__btn--vol-up { left: -1.4%; top: 23%;   width: 1.5%; height: 7.8%; border-radius: 2px 0 0 2px; }
.phone__btn--vol-dn { left: -1.4%; top: 32.5%; width: 1.5%; height: 7.8%; border-radius: 2px 0 0 2px; }
.phone__btn--power  { right: -1.4%; top: 26%;  width: 1.5%; height: 10.5%; border-radius: 0 2px 2px 0; }

.phone__status {
    flex: none;
    display: flex; align-items: center; justify-content: space-between;
    padding: 4.4% 7% 1.6%;
    font-weight: 700; font-size: .6rem; color: var(--ink);
}
.phone__icons { display: inline-flex; align-items: center; gap: .18rem; }
.phone__icons svg { display: block; }

.phone__home {
    flex: none; width: 34%; height: 3.5px;
    border-radius: 999px; background: var(--outline);
    opacity: .3; margin: .3rem auto .45rem;
}

.phone__view { flex: 1; min-height: 0; position: relative; overflow: hidden; }

/* ------------------------------------------------------------------ map --- */

.mapview { position: absolute; inset: 0; background: #E2EDF7; }
.mapview svg { display: block; width: 100%; height: 100%; }

/*
    Ground, parks, water — in that order of area.

    The base used to be the water colour, which made every map read as a sea
    with green islands in it rather than as a city. Ground is the warm neutral
    a paper map uses; green is a park; blue is actually water.
*/
.map__ground { fill: #EDE9E0; }
.map__land { fill: #D4EBDB; }
.map__water { fill: #BEDDF4; }
.map__block { fill: #F1F6FC; stroke: #C3D4E6; stroke-width: 2; }
.map__road-case { fill: none; stroke: var(--outline); stroke-width: 15; stroke-linecap: round; opacity: .85; }
.map__road { fill: none; stroke: #FFFFFF; stroke-width: 10; stroke-linecap: round; }

/*
    Markers.

    The pin's tail is drawn as part of one path rather than as a rotated square:
    width and length have to move independently, which a rotated square cannot
    manage — that shape only lengthens by widening, and past a point it is a
    diamond rather than a pin. The point sits at the bottom, so a pin marks the
    place it is actually over.
*/
.pin { transform-box: fill-box; transform-origin: center bottom; }
.pin__body { fill: var(--pink); stroke: var(--outline); stroke-width: 2.5; stroke-linejoin: round; }
.pin__dot { fill: #FFF; }
.pin--locked .pin__body { fill: var(--sky); }
.pin--open .pin__body { fill: var(--grass); }
.pin--cluster .pin__body { fill: var(--violet); }
.pin__count { font-family: var(--display); font-weight: 700; font-size: 11px; fill: #FFF; }

.pin--pop { animation: pop .5s cubic-bezier(.2, 1.5, .4, 1) both; }
@keyframes pop {
    0%   { opacity: 0; transform: scale(.3) translateY(7px); }
    60%  { opacity: 1; }
    100% { opacity: 1; transform: scale(1) translateY(0); }
}

.map__radius { fill: var(--pink); fill-opacity: .13; stroke: var(--pink); stroke-width: 2; stroke-dasharray: 5 5; }

.map__chip {
    position: absolute;
    font-family: var(--display); font-weight: 600; font-size: .7rem;
    color: var(--outline);
    background: var(--card);
    border: 2.5px solid var(--outline);
    border-radius: 999px;
    padding: .22rem .6rem;
    /* Map tiles are not an app surface, so an overlay carries its own
       separation rather than relying on what is behind it. */
    box-shadow: var(--drop-sm);
    white-space: nowrap;
}

/* Preview sheet inside the phone */
.sheet {
    position: absolute; left: 5%; right: 5%; bottom: 4%;
    background: var(--card);
    border: 2.5px solid var(--outline);
    border-radius: 14px;
    box-shadow: var(--drop-sm);
    padding: .6rem .7rem;
    z-index: 4;
}
.sheet__title { font-family: var(--display); font-weight: 600; font-size: .82rem; line-height: 1.2; }
.sheet__meta { font-size: .68rem; color: var(--ink-mute); margin-top: .15rem; }
.sheet__row { display: flex; align-items: center; gap: .45rem; }
.sheet__face {
    width: 1.5rem; height: 1.5rem; flex: none;
    border-radius: 999px; border: 2px solid var(--outline);
    display: grid; place-items: center;
    font-family: var(--display); font-weight: 700; font-size: .62rem; color: #FFF;
}

/* --------------------------------------------------------- big map board -- */

.board {
    position: relative;
    border: 3px solid var(--outline);
    border-radius: 22px;
    box-shadow: var(--drop);
    overflow: hidden;
    background: #E2EDF7;
}
.board svg { display: block; width: 100%; height: auto; }

.board__photo {
    position: absolute;
    width: 6.5rem;
    padding: .35rem .35rem 0;
    background: var(--card);
    border: 2.5px solid var(--outline);
    border-radius: 7px;
    box-shadow: var(--drop-sm);
}
.board__photo img { display: block; width: 100%; height: 4.25rem; object-fit: cover; border-radius: 4px; }
.board__photo figcaption {
    font-family: var(--display); font-weight: 600; font-size: .66rem;
    color: var(--ink-soft); padding: .25rem .1rem .3rem;
}

/* ------------------------------------------------------------- 3 steps ---- */

.steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.5rem; margin-top: 2.5rem; }
.step { padding: 1.7rem 1.5rem 1.8rem; display: flex; flex-direction: column; gap: .55rem; position: relative; }
.step:nth-child(1) { transform: rotate(-1.3deg); }
.step:nth-child(2) { transform: rotate(.7deg); }
.step:nth-child(3) { transform: rotate(-.5deg); }
.step__n {
    font-family: var(--display); font-weight: 700; font-size: 2.6rem;
    line-height: 1; color: var(--pink); -webkit-text-stroke: 0;
}
.step:nth-child(2) .step__n { color: var(--sky-deep); }
.step:nth-child(3) .step__n { color: var(--grass); }
.step h3 { font-size: var(--t-md); }
.step p { font-size: var(--t-sm); color: var(--ink-soft); line-height: 1.6; }

/* ------------------------------------------------------- sticky storytelling */

.story__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(2rem, 5vw, 4rem);
    align-items: start;
}
/*
    The phone is centred in the viewport for as long as the section is in it,
    rather than pinned just under the bar. Pinned to the top, the first chapter
    — which is itself centred in its own tall block — begins half a screen
    lower than the phone, and the section opens looking like two unrelated
    columns.
*/
.story__stage {
    position: sticky;
    top: var(--bar);
    height: calc(100vh - var(--bar));
    display: grid; place-items: center;
}
.story__chapters { display: grid; }
.chapter {
    min-height: 72vh;
    display: flex; flex-direction: column; justify-content: center;
    gap: .8rem;
    opacity: .32;
    transition: opacity .35s ease;
}
.chapter[data-active="true"] { opacity: 1; }
.chapter__when {
    font-family: var(--display); font-weight: 600; font-size: var(--t-xs);
    letter-spacing: .07em; text-transform: uppercase; color: var(--pink);
}
.chapter h3 { font-size: var(--t-xl); }
.chapter__place {
    display: inline-flex; align-items: center; gap: .35rem;
    font-family: var(--display); font-weight: 600; font-size: var(--t-sm);
    color: var(--ink-mute);
}
.chapter p { font-size: var(--t-md); color: var(--ink-soft); max-width: 32rem; }

.story__slide { position: absolute; inset: 0; opacity: 0; transition: opacity .4s ease; }
.story__slide[data-active="true"] { opacity: 1; }

/* --------------------------------------------------------------- bento ---- */

.bento {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1.1rem;
    margin-top: 2.5rem;
}
.tile {
    padding: 1.5rem 1.4rem 1.6rem;
    display: flex; flex-direction: column; gap: .45rem;
    background: var(--card);
    border: 3px solid var(--outline);
    border-radius: 18px;
    box-shadow: var(--drop);
    transition: transform .14s ease, box-shadow .14s ease;
}
.tile:hover { transform: translateY(-3px); box-shadow: var(--drop-lg); }
.tile h3 { font-size: var(--t-md); }
.tile p { font-size: var(--t-sm); color: var(--ink-soft); line-height: 1.55; }
.tile__badge {
    width: 2.9rem; height: 2.9rem;
    border-radius: 999px;
    border: 3px solid var(--outline);
    display: grid; place-items: center;
    margin-bottom: .35rem;
    box-shadow: var(--drop-sm);
    background: var(--sun);
}
.tile--wide { grid-column: span 2; }
.tile--tall { grid-row: span 2; }
.tile--pink { background: var(--pink-soft); }
.tile--sky { background: var(--sky-soft); }
.tile--sun { background: var(--sun-soft); }
.tile--grass { background: var(--grass-soft); }
.tile--violet { background: var(--violet-soft); }

/* ----------------------------------------------------------- five years --- */

/*
    A scatter of prints and notes.

    Columns rather than absolute positions. Absolute placement gives a pleasing
    scatter at exactly one width and overlapping rubble at every other, and it
    cannot let a photograph keep its own height. Columns stagger on their own
    *because* the prints are different shapes — a portrait one pushes the
    column below it down, which is the scatter, earned rather than hand-placed.
*/
.scatter {
    columns: 3;
    column-gap: clamp(1.25rem, 3vw, 2.25rem);
    margin-top: 2.5rem;
}

.keepsake {
    break-inside: avoid;
    /* `inline-block` as well as the margin: a bare block still breaks across
       columns in some browsers, and half a polaroid at the foot of a column is
       the one thing this layout must never do. */
    display: inline-block;
    width: 100%;
    margin: 0 0 clamp(1.4rem, 3vw, 2.1rem);
    position: relative;

    background: var(--card);
    border: 3px solid var(--outline);
    border-radius: 10px;
    box-shadow: var(--drop);
    padding: .7rem .7rem 0;
}

/* A strip of tape, as though it were stuck down by hand. */
.keepsake::before {
    content: "";
    position: absolute;
    top: -.8rem; left: 50%;
    width: 5rem; height: 1.6rem;
    transform: translateX(-50%) rotate(-4deg);
    background: var(--tape);
    border: 2.5px solid var(--outline);
    border-radius: 3px;
}

.keepsake__img {
    display: block;
    /*
        The print keeps the shape it was taken in — no `object-fit`, so nothing
        is ever cropped. A grid of squares is a photo library; an album shows
        the picture somebody actually took.

        What is bounded is the *height*, and by shrinking the width to match
        rather than by cropping. Without it a portrait photograph renders twice
        the height of a landscape one, takes a whole column to itself, and the
        balancer never needs a third — the scatter collapses to two columns with
        a hole where the third should be. A tall print now sits centred on a
        wider mount, which is how a portrait photograph is actually mounted.
    */
    max-width: 100%;
    max-height: 21rem;
    width: auto;
    height: auto;
    margin-inline: auto;
    border: 2.5px solid var(--outline);
    border-radius: 5px;
}

.keepsake__cap,
.keepsake--note {
    font-family: var(--display);
    font-weight: 600;
    font-size: var(--t-base);
    line-height: 1.3;
    color: var(--ink);
}
.keepsake__cap { padding: .7rem .3rem .8rem; }

.keepsake small {
    display: block;
    margin-top: .2rem;
    font-family: var(--body);
    font-weight: 400;
    font-size: var(--t-xs);
    line-height: 1.5;
    color: var(--ink-mute);
}

/* A written memory, with no photograph and none invented. */
.keepsake--note {
    padding: 1.1rem 1.2rem 1.2rem;
    border-radius: 14px;
}
.keepsake--note::before { transform: translateX(-50%) rotate(3deg); }
.keepsake--sun { background: var(--sun-soft); }
.keepsake--pink { background: var(--pink-soft); }

/*
    Kept under four degrees, and varied in magnitude as well as sign — six
    angles of one size alternating left and right reads as a pattern rather
    than as prints dropped on a page. Past that ceiling it stops looking
    placed by hand and starts looking like broken layout, and because `rotate`
    does not change an element's layout box, the corners begin to overhang far
    enough to clip against the column beside them.
*/
.keepsake:nth-child(5n+1) { transform: rotate(-2.2deg); }
.keepsake:nth-child(5n+2) { transform: rotate(1.4deg); }
.keepsake:nth-child(5n+3) { transform: rotate(2.8deg); }
.keepsake:nth-child(5n+4) { transform: rotate(-1.1deg); }
.keepsake:nth-child(5n+5) { transform: rotate(1.9deg); }

/* -------------------------------------------------------------- shared ---- */

.faces { display: flex; align-items: center; }
.faces span {
    width: 2.4rem; height: 2.4rem;
    border-radius: 999px;
    border: 2.5px solid var(--outline);
    display: grid; place-items: center;
    font-family: var(--display); font-weight: 700; font-size: .8rem;
    color: #FFF;
    margin-left: -.6rem;
    box-shadow: var(--drop-sm);
}
.faces span:first-child { margin-left: 0; }

.trip { display: grid; gap: .9rem; margin-top: 1.8rem; }
.contrib {
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .85rem;
    align-items: start;
    padding: .9rem 1.1rem;
    background: var(--card);
    border: 2.5px solid var(--outline);
    border-radius: 14px;
    box-shadow: var(--drop-sm);
}
.contrib:nth-child(odd) { transform: rotate(-.5deg); }
.contrib:nth-child(even) { transform: rotate(.45deg); }
.contrib b { font-family: var(--display); font-weight: 600; display: block; font-size: var(--t-sm); }
.contrib p { font-size: var(--t-sm); color: var(--ink-soft); margin-top: .1rem; }
.contrib__what {
    display: inline-flex; align-items: center; gap: .3rem;
    font-family: var(--display); font-weight: 600; font-size: .72rem;
    text-transform: uppercase; letter-spacing: .05em;
    color: var(--ink-mute); margin-top: .3rem;
}

/* ------------------------------------------------------------- privacy ---- */

.locks { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.4rem; margin-top: 2.5rem; }
.lock { padding: 1.6rem 1.5rem 1.75rem; display: flex; flex-direction: column; gap: .55rem; }
.lock:nth-child(1) { transform: rotate(-1.3deg); }
.lock:nth-child(2) { transform: rotate(.7deg); }
.lock:nth-child(3) { transform: rotate(-.5deg); }
.lock__emoji { font-size: 1.9rem; line-height: 1; }
.lock h3 { font-size: var(--t-md); }
.lock p { font-size: var(--t-sm); color: var(--ink-soft); line-height: 1.6; }

/* ------------------------------------------------------------- screens ---- */

.rail {
    display: flex; gap: 1.5rem;
    overflow-x: auto;
    padding: 1rem var(--gutter) 2rem;
    margin-inline: calc(var(--gutter) * -1);
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
}
.rail::-webkit-scrollbar { display: none; }
.screen { flex: none; width: 15rem; scroll-snap-align: center; }
.screen__cap {
    margin-top: .9rem; text-align: center;
    font-family: var(--display); font-weight: 600; font-size: var(--t-sm);
}
.screen__cap small { display: block; font-family: var(--body); font-weight: 400; font-size: var(--t-xs); color: var(--ink-mute); }

/* A simplified screen rendered inside a phone frame. */
.ui { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--card); }
.ui__bar {
    flex: none; padding: .55rem .7rem;
    font-family: var(--display); font-weight: 600; font-size: .78rem;
    border-bottom: 2px solid rgba(31, 43, 87, .1);
}
.ui__body { flex: 1; min-height: 0; overflow: hidden; padding: .55rem; display: grid; gap: .45rem; align-content: start; }
.ui__row {
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .45rem; align-items: center;
    padding: .4rem .45rem;
    border: 2px solid rgba(31, 43, 87, .12);
    border-radius: 9px;
}
.ui__thumb { width: 2rem; height: 2rem; border-radius: 6px; border: 2px solid var(--outline); object-fit: cover; display: block; }
.ui__t { font-family: var(--display); font-weight: 600; font-size: .68rem; line-height: 1.2; }
.ui__s { font-size: .6rem; color: var(--ink-mute); }
.ui__photo { width: 100%; border-radius: 8px; border: 2px solid var(--outline); display: block; }
.ui__tabs {
    flex: none; display: flex; justify-content: space-around;
    padding: .4rem 0 .5rem;
    border-top: 2px solid rgba(31, 43, 87, .1);
}
.ui__tabs span { width: 1.1rem; height: 1.1rem; border-radius: 5px; background: rgba(31, 43, 87, .16); }
.ui__tabs span:first-child { background: var(--pink); }

/* -------------------------------------------------------- testimonials ---- */

.quotes { columns: 3; column-gap: 1.2rem; margin-top: 2.5rem; }
.quote {
    break-inside: avoid;
    margin-bottom: 1.2rem;
    padding: 1.3rem 1.35rem 1.4rem;
    background: var(--card);
    border: 3px solid var(--outline);
    border-radius: 16px;
    box-shadow: var(--drop);
}
.quote:nth-child(3n+1) { transform: rotate(-1.1deg); }
.quote:nth-child(3n+2) { transform: rotate(.9deg); background: var(--sun-soft); }
.quote:nth-child(3n)   { transform: rotate(-.4deg); background: var(--sky-soft); }
/* The browser's own `blockquote` margin is 1em 40px — eighty pixels of it
   inside a three-column card, which squeezes a quote down to four words a line
   and makes every card look broken. */
.quote blockquote { margin: 0; }
.quote p { font-size: var(--t-base); line-height: 1.55; }
/* `figcaption`, not `footer`: the quote is a `<figure>`, so its attribution is
   the figure's caption. Targeting `footer` here silently did nothing and left
   the avatar stacked above the name. */
.quote figcaption {
    display: flex; align-items: center; gap: .55rem;
    margin-top: .9rem;
    font-family: var(--display); font-weight: 600; font-size: var(--t-sm);
}
.quote figcaption span:not([class]) { color: var(--ink-mute); font-weight: 500; font-size: var(--t-xs); }

/* ----------------------------------------------------------------- faq ---- */

.faq { display: grid; gap: .8rem; margin-top: 2.25rem; max-width: 48rem; }
.faq details {
    background: var(--card);
    border: 3px solid var(--outline);
    border-radius: 14px;
    box-shadow: var(--drop-sm);
    overflow: hidden;
}
.faq summary {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    padding: 1rem 1.2rem;
    font-family: var(--display); font-weight: 600; font-size: var(--t-md);
    cursor: pointer;
    list-style: none;
    min-height: 48px;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
    content: "+";
    font-family: var(--display); font-weight: 600; font-size: 1.4rem;
    line-height: 1; color: var(--pink); flex: none;
}
.faq details[open] summary::after { content: "–"; }
.faq p { padding: 0 1.2rem 1.15rem; color: var(--ink-soft); font-size: var(--t-sm); line-height: 1.65; }

/* ------------------------------------------------------------- waitlist --- */

.signup {
    padding: clamp(1.75rem, 4vw, 2.75rem);
    background: var(--card);
    border: 3px solid var(--outline);
    border-radius: 22px;
    box-shadow: var(--drop);
}

.form { display: grid; gap: 1rem; margin-top: 1.5rem; }
.form__row { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }

label.lbl {
    display: block;
    font-family: var(--display); font-weight: 600; font-size: var(--t-sm);
    margin-bottom: .4rem;
}
label.lbl span { color: var(--ink-mute); font-weight: 500; }

.input {
    width: 100%;
    min-height: 48px;
    padding: .7rem .95rem;
    background: var(--paper);
    border: 2.5px solid var(--outline);
    border-radius: 12px;
    font: inherit; font-size: var(--t-base);
    color: var(--ink);
}
.input:focus { outline: 3px solid var(--pink); outline-offset: 1px; }
textarea.input { min-height: 9rem; resize: vertical; line-height: 1.6; }

.radios { display: flex; flex-wrap: wrap; gap: .6rem; }
.radio {
    display: inline-flex; align-items: center; gap: .45rem;
    min-height: 48px; padding: .5rem 1rem;
    border: 2.5px solid var(--outline);
    border-radius: 999px;
    background: var(--paper);
    font-family: var(--display); font-weight: 600; font-size: var(--t-sm);
    cursor: pointer;
}
.radio input { accent-color: var(--pink); width: 1.05rem; height: 1.05rem; }
.radio:has(input:checked) { background: var(--sun); box-shadow: var(--drop-sm); }

.check { display: flex; gap: .65rem; align-items: flex-start; font-size: var(--t-sm); }
.check input { margin-top: .3rem; accent-color: var(--pink); width: 1.05rem; height: 1.05rem; flex: none; }

.err { margin-top: .4rem; font-size: var(--t-sm); font-weight: 700; color: #C02B3B; }

.said {
    display: flex; align-items: flex-start; gap: .7rem;
    padding: 1rem 1.15rem;
    border: 3px solid var(--outline);
    border-radius: 14px;
    background: var(--grass-soft);
    box-shadow: var(--drop-sm);
    font-weight: 600;
}
.said--bad { background: var(--pink-soft); }

/* A field no person ever sees, and no person ever fills in. */
.trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ------------------------------------------------------------- newsletter - */

.news {
    display: grid; gap: .75rem;
    max-width: 30rem;
}
.news__row { display: flex; gap: .6rem; flex-wrap: wrap; }
.news__row .input { flex: 1 1 12rem; }

/* ---------------------------------------------------------------- close --- */

.close__card {
    padding: clamp(2.25rem, 5vw, 3.5rem);
    background: var(--pink);
    text-align: center;
    transform: rotate(-.7deg);
}
.close__card h2 { font-size: clamp(1.9rem, 4.5vw, 3rem); color: #FFF; max-width: 20ch; margin-inline: auto; }
.close__card p { color: #FFF; margin-top: 1rem; font-size: var(--t-md); }
.close__cta { margin-top: 2rem; display: flex; justify-content: center; gap: .8rem; flex-wrap: wrap; }

/* --------------------------------------------------------------- footer --- */

.foot {
    background: var(--outline);
    color: #FFF;
    padding-block: clamp(2.5rem, 5vw, 3.5rem);
    /*
        The only dark region on the site, so it is the only one that has to tell
        the browser so. Without this the newsletter checkbox renders as a dark
        square on a dark ground and looks like it is already ticked — or missing.
    */
    color-scheme: dark;
}
.foot a { color: #FFF; }
.foot__inner { display: grid; grid-template-columns: minmax(0, 1.4fr) repeat(2, minmax(0, 1fr)); gap: 2rem; }
.foot__logo { display: flex; align-items: center; gap: .55rem; font-family: var(--display); font-weight: 600; font-size: 1.125rem; }
.foot__logo img { width: 34px; height: 34px; border-radius: 9px; }
.foot h4 { font-size: var(--t-sm); letter-spacing: .06em; text-transform: uppercase; opacity: .65; margin-bottom: .7rem; }
.foot ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .45rem; font-size: var(--t-sm); }
.foot ul a:hover { text-decoration: underline; text-underline-offset: 3px; }
.foot__social { display: flex; gap: .5rem; margin-top: 1rem; }
.foot__social a {
    width: 40px; height: 40px;
    display: grid; place-items: center;
    border: 2px solid rgba(255, 255, 255, .3);
    border-radius: 999px;
}
.foot__social a:hover { border-color: #FFF; }
.foot__legal {
    margin-top: 2.5rem; padding-top: 1.5rem;
    border-top: 1px solid rgba(255, 255, 255, .18);
    display: flex; flex-wrap: wrap; gap: .5rem 1.5rem;
    justify-content: space-between;
    font-size: var(--t-xs); opacity: .75;
}

/* ---------------------------------------------------------------- prose --- */

.prose { max-width: 44rem; }
.prose h2 { font-size: var(--t-lg); margin-top: 2.5rem; margin-bottom: .7rem; }
.prose h3 { font-size: var(--t-md); margin-top: 1.75rem; margin-bottom: .5rem; }
.prose p, .prose li { color: var(--ink-soft); line-height: 1.72; }
.prose p + p { margin-top: 1rem; }
.prose ul { padding-left: 1.2rem; display: grid; gap: .5rem; margin: .9rem 0 0; }
.prose strong { color: var(--ink); }
.prose__updated {
    display: inline-block;
    font-family: var(--display); font-weight: 600; font-size: var(--t-xs);
    padding: .25rem .7rem;
    border: 2px solid var(--outline); border-radius: 999px;
    margin-top: 1rem;
}

/* -------------------------------------------------------------- reveal ---- */

/*
    Scroll reveal.

    Applied by JavaScript adding `data-reveal` handling, never by CSS alone: a
    page that hides its own content and waits for a script is a page that shows
    nothing if the script never runs. The default is visible; the class only
    goes on once the observer exists to take it off again.
*/
[data-reveal].is-armed { opacity: 0; transform: translateY(18px); }
[data-reveal].is-in { opacity: 1; transform: none; transition: opacity .6s ease, transform .6s cubic-bezier(.2, .7, .3, 1); }

/* ---------------------------------------------------------- responsive ---- */

@media (max-width: 68rem) {
    .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .tile--tall { grid-row: span 1; }
    .quotes { columns: 2; }
    .scatter { columns: 2; }
    .foot__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

@media (max-width: 62rem) {
    .hero__inner, .story__inner { grid-template-columns: minmax(0, 1fr); }
    .steps, .locks { grid-template-columns: minmax(0, 1fr); }
    .step, .lock { transform: none; }

    /*
        The cards stop floating the moment the hero stops being two columns.

        This has to happen at the *same* breakpoint as `.hero__inner`, not a
        narrower one. The cards are positioned at -12% of the stage, which is a
        sensible overhang while the stage is a narrow column beside the text
        and sixty pixels off the edge of the page once the stage is full width.
        Splitting the two rules across 62rem and 48rem left a tablet-sized
        window where the hero overflowed horizontally.

        There is no width at which a 180px card can overlap a 240px phone by
        only its edge — it ends up covering the screen the phone is there to
        show. Stacked underneath, the same cards still say "memories, at
        places", which is the whole job they were doing.
    */
    .hero__stage { min-height: 0; display: grid; gap: .9rem; justify-items: center; }
    /* The phone leads. It is last in the markup because it sits on top of the
       cards when they float; stacked, it should come first. */
    .hero__stage .phone { order: -1; width: min(15rem, 74%); }
    .threads { display: none; }
    .memcard {
        position: static;
        width: 100%;
        max-width: 22rem;
        animation: none;
        transform: none;
    }
    .memcard--3 { display: none; }
    .story__stage { position: static; height: auto; margin-bottom: 2rem; }
    .chapter { min-height: auto; padding-block: 1.75rem; opacity: 1; }
    .nav__links { display: none; }
    /* The sheet behind the burger carries the same call to action, so a second
       copy in the bar is one control too many for the width available. */
    .nav__cta { display: none; }
    .nav__burger { display: flex; }
}

@media (max-width: 48rem) {
    .bento { grid-template-columns: minmax(0, 1fr); }
    .tile--wide { grid-column: span 1; }
    .quotes { columns: 1; }
    .foot__inner { grid-template-columns: minmax(0, 1fr); }
    /* One column on a handset, and the prints straighten up: a rotated card at
       full width overhangs the gutter on both sides. */
    .scatter { columns: 1; }
    .keepsake,
    .keepsake:nth-child(5n+1),
    .keepsake:nth-child(5n+2),
    .keepsake:nth-child(5n+3),
    .keepsake:nth-child(5n+4),
    .keepsake:nth-child(5n+5) { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
    html { scroll-behavior: auto; }
    [data-reveal].is-armed { opacity: 1; transform: none; }
}
