/* ==========================================================================
   LaSalle Street Advisors — public website

   Ported from the single-file design source. The original carried every rule as
   an inline style attribute; the values here are the same, lifted into tokens
   and classes so the two pages stay consistent and the site is editable.

   One deliberate change: the original set footer text and the inactive nav item
   in #938d84, which is 2.9:1 against the cream background and fails WCAG AA.
   Both now use #6f6a63 — a grey already in the design, at 4.95:1.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Newsreader, served from this origin

   Previously loaded from fonts.googleapis.com, which cost a second DNS lookup,
   TLS handshake and render-blocking stylesheet on every page load, and made the
   firm's typography depend on a third party being reachable.

   One variable file per subset covers every weight both sites use — the axis
   spans 300–500, so the 300 the hero wants and the 500 the intranet wants come
   from the same download. The italic face the old request also pulled is gone;
   nothing on either site sets font-style: italic.

   `unicode-range` is what makes shipping latin-ext free: the browser fetches it
   only if a glyph on the page actually needs it, so ordinary English copy costs
   the latin file alone.

   Newsreader is licensed under the SIL Open Font License 1.1 — see OFL.txt
   alongside the woff2 files, which the license requires be distributed with it.
   -------------------------------------------------------------------------- */

@font-face {
    font-family: Newsreader;
    font-style: normal;
    font-weight: 300 500;
    font-display: swap;
    src: url("../fonts/newsreader-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
        U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: Newsreader;
    font-style: normal;
    font-weight: 300 500;
    font-display: swap;
    src: url("../fonts/newsreader-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
        U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
        U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
    --bg: #f8f6f2;
    --ink: #2b2724;
    --ink-muted: #55504a;
    --ink-faint: #6f6a63;
    --accent: #3d4a5c;
    --rule: #ddd8d0;
    --portrait-bg: #efece6;
    --selection: #e7e2d9;

    --font-serif: Newsreader, Georgia, "Times New Roman", serif;
    --font-sans: "Helvetica Neue", Helvetica, Arial, sans-serif;

    --measure: 1180px;
    --gutter: 40px;

    --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
    /* Long enough to read as a fade rather than a snap, short enough not to lag
       the pointer. Shared so hover feel stays consistent across the site. */
    --dur-hover: 260ms;
}

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

html, body { margin: 0; padding: 0; }

body {
    background: var(--bg);
    color: var(--ink);
    font-family: var(--font-serif);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

::selection { background: var(--selection); }

a {
    color: inherit;
    text-decoration: none;
    transition: color var(--dur-hover) var(--ease);
}
a:hover { color: var(--accent); }

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* --------------------------------------------------------------------------
   Motion
   -------------------------------------------------------------------------- */

@keyframes lsRise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}
@keyframes lsFade {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes lsRule {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

/* Every animation here is decorative — each one's resting state is the visible
   one, so switching them off leaves the page fully legible rather than blank.
   Transitions are included now that hover states use them. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation: none !important;
        transition: none !important;
    }
}

/* --------------------------------------------------------------------------
   Shell
   -------------------------------------------------------------------------- */

.page {
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.main {
    position: relative;
    z-index: 1;
    flex: 1;
    width: 100%;
    max-width: var(--measure);
    margin: 0 auto;
    padding: 0 var(--gutter);
}

.rule {
    height: 1px;
    background: var(--rule);
    transform-origin: left;
    animation: lsRule 1.1s var(--ease) 0.5s both;
}

/* Carries the above-footer hairline in the same box as .main and
   .footer__inner — same measure, same gutter — so all three align at every
   breakpoint. flex:none stops the column from collapsing a 1px child. */
.rule-band {
    flex: none;
    width: 100%;
    max-width: var(--measure);
    margin: 0 auto;
    padding: 0 var(--gutter);
}

/* It is the last thing on the page, so a fixed delay would draw it long before
   most viewports reach it. Held closed until scrolled to; see the scroll-driven
   block below, which supersedes this where the browser supports it. */
.rule--footer { animation-delay: 0.15s; }

/* --------------------------------------------------------------------------
   Masthead
   -------------------------------------------------------------------------- */

.masthead {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 32px;
    width: 100%;
    max-width: var(--measure);
    margin: 0 auto;
    padding: 34px var(--gutter) 0;
    animation: lsFade 0.9s ease both;
}

.lockup { display: block; line-height: 1; }

.lockup__name {
    display: block;
    font-size: 20px;
    letter-spacing: 0.005em;
}

.lockup__suffix {
    display: block;
    margin-top: 3px;
    color: var(--ink-faint);
    font-family: var(--font-sans);
    font-size: 9.5px;
    font-weight: 500;
    letter-spacing: 0.34em;
    text-transform: uppercase;
}

.nav {
    display: flex;
    gap: 30px;
    font-family: var(--font-sans);
    font-size: 10.5px;
    font-weight: 500;
    letter-spacing: 0.2em;
    text-transform: uppercase;
}

.nav__link {
    padding-bottom: 4px;
    border-bottom: 1px solid transparent;
    color: var(--ink-faint);
    transition: color var(--dur-hover) var(--ease),
                border-bottom-color var(--dur-hover) var(--ease);
}

/* Hovering an inactive item hints the underline in at rule weight, so it reads
   as approaching the current state rather than becoming it. */
.nav__link:hover { border-bottom-color: var(--rule); }
.nav__link--current:hover { border-bottom-color: var(--accent); }

.nav__link--current {
    border-bottom-color: var(--accent);
    color: var(--ink);
}

/* --------------------------------------------------------------------------
   Firm page
   -------------------------------------------------------------------------- */

/* Skyline behind the masthead and hero, dissolving into the page background.
   Decorative and non-interactive. */
.hero-bleed {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    /* A fixed 760px band ends less than halfway down a tall display, leaving the
       skyline to stop abruptly above a large flat area. Growing it with the
       viewport keeps the dissolve in proportion; the floor holds the designed
       height on a laptop, where 64vh would be shorter. */
    height: max(760px, 64vh);
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
}

.hero-bleed__image,
.hero-bleed__wash {
    position: absolute;
    inset: 0;
}

.hero-bleed__image {
    width: 100%;
    height: 100%;
    /* The same fitting algorithm as the video layers, deliberately:
       background-size: cover rounds differently from object-fit: cover at
       fractional device-pixel scales, and on a Retina display that landed the
       still and the clip on slightly different pixels — the handover
       shimmered on the skyline's edges. One algorithm, one geometry. */
    object-fit: cover;
    object-position: 50% 34%;
    /* Sepia over full grayscale keeps the footage monochrome but tints it
       toward the palette's warm greys — plain grayscale reads cool against
       the cream wash. */
    filter: grayscale(1) sepia(0.16) contrast(1.02);
    animation: lsFade 1.6s ease both;
}

/* The clips sit on the still, never replace it, so anything that stops them
   playing falls back to the designed treatment rather than to a blank band.
   Same crop, desaturation and contrast as the still, so the handover from the
   poster frame to the first clip is not visible as a colour shift.

   Both layers stay at opacity 0 until the script fades one up: if it never runs
   — no JavaScript, an old browser, a blocked autoplay — nothing here is ever
   seen, and the still is what the page shows. */
.hero-bleed__video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 34%;
    filter: grayscale(1) sepia(0.16) contrast(1.02);
    opacity: 0;
    transition: opacity 1.2s var(--ease);
}

.hero-bleed__video.is-showing { opacity: 1; }

.hero-bleed__wash {
    background: linear-gradient(
        to bottom,
        rgba(248, 246, 242, 0.62) 0%,
        rgba(248, 246, 242, 0.5) 34%,
        rgba(248, 246, 242, 0.88) 72%,
        var(--bg) 100%
    );
}

/* `cover` scales the crop up with viewport width, which flattens the skyline
   into haze on a large display. A little less desaturation and a touch more
   contrast holds it together. The wash is deliberately left alone — it is what
   protects the headline's contrast. */
@media (min-width: 1600px) {
    .hero-bleed__image,
    .hero-bleed__video { filter: grayscale(1) sepia(0.16) contrast(1.06); }
}

.eyebrow {
    margin: 0;
    color: var(--accent);
    font-family: var(--font-sans);
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.3em;
    text-transform: uppercase;
    animation: lsFade 0.9s ease 0.05s both;
}

/* .main is flex:1, so on a tall viewport every spare pixel collected *below* the
   hero and the page read top-heavy with a void above the footer. Centring
   distributes that slack around the hero instead. Where there is no slack — a
   laptop, or any narrow viewport — this changes nothing. */
.main--hero {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.hero { padding: 148px 0 132px; max-width: 900px; }

/* Larger than the eyebrow elsewhere on purpose. At 10px the accent blue is a
   hairline of colour and reads as grey against the wash; the extra weight of
   ink is what lets the one coloured element on the page actually register.
   Tracking eases off as the size goes up — 0.3em set at 13px pushes the line
   wider than the headline's first line, which breaks the left-edge rhythm. */
.hero .eyebrow {
    margin-bottom: 40px;
    font-size: 13px;
    letter-spacing: 0.26em;
    animation-delay: 0.1s;
}

.hero__headline {
    margin: 0;
    font-size: clamp(38px, 5.6vw, 74px);
    font-weight: 300;
    line-height: 1.06;
    letter-spacing: -0.022em;
    text-wrap: pretty;
    animation: lsRise 1s var(--ease) 0.18s both;
}

.hero__subhead {
    margin: 40px 0 0;
    max-width: 560px;
    color: var(--ink-muted);
    font-size: 20px;
    font-weight: 300;
    line-height: 1.62;
    text-wrap: pretty;
    animation: lsRise 1s var(--ease) 0.34s both;
}

/* --------------------------------------------------------------------------
   Team page
   -------------------------------------------------------------------------- */

.page-head { padding: 110px 0 72px; max-width: 760px; }

.page-head + .rule { animation-duration: 1s; animation-delay: 0.3s; }

.roster { padding-bottom: 140px; }

.person {
    display: grid;
    grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
    gap: 56px;
    align-items: start;
    padding: 56px 0;
    border-bottom: 1px solid var(--rule);
    animation: lsRise 0.9s var(--ease) both;
}

/* Fallback stagger, so the roster arrives as a sequence rather than as one
   block. Superseded by the scroll-driven block below where it is supported,
   which reveals each person against their own scroll position instead. */
/* The border is a separator between people, so the last one has nothing to
   separate — it left a stray hairline that read as a second, misplaced footer
   rule at the end of the roster. */
.person:last-child { border-bottom: none; }

.person:nth-child(2) { animation-delay: 0.1s; }
.person:nth-child(3) { animation-delay: 0.2s; }
.person:nth-child(n + 4) { animation-delay: 0.3s; }

.person__portrait {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 5;
    background-color: var(--portrait-bg);
    object-fit: cover;
    object-position: 50% 22%;
    filter: var(--tone, grayscale(1));
}

.person__body { max-width: 640px; }

.person__name {
    margin: 0;
    font-size: 30px;
    font-weight: 400;
    letter-spacing: -0.015em;
}

.person__role {
    margin: 8px 0 26px;
    color: var(--accent);
    font-family: var(--font-sans);
    font-size: 10.5px;
    font-weight: 500;
    letter-spacing: 0.2em;
    text-transform: uppercase;
}

.person__bio {
    margin: 0;
    color: var(--ink-muted);
    font-size: 16.5px;
    font-weight: 300;
    line-height: 1.72;
    text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   Not found
   -------------------------------------------------------------------------- */

.page-head--tall { padding-bottom: 160px; }

.notfound__headline {
    margin: 34px 0 0;
    font-size: clamp(30px, 3.4vw, 44px);
    font-weight: 300;
    line-height: 1.12;
    letter-spacing: -0.02em;
}

.notfound__body {
    margin: 28px 0 0;
    color: var(--ink-muted);
    font-size: 18px;
    font-weight: 300;
}

.link {
    border-bottom: 1px solid var(--rule);
    transition: color var(--dur-hover) var(--ease),
                border-bottom-color var(--dur-hover) var(--ease);
}
.link:hover { border-bottom-color: var(--accent); }

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

/* No border-top: the hairline above the footer is .rule-band, which is inset to
   the measure and animates. A border here duplicated it full-bleed, so tall
   viewports showed two lines at different widths. */
.footer { margin-top: 0; }

.footer__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px 40px;
    max-width: var(--measure);
    margin: 0 auto;
    padding: 30px var(--gutter) 44px;
    color: var(--ink-faint);
    font-family: var(--font-sans);
    font-size: 10.5px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   Scroll-driven reveals

   Where the browser supports scroll-driven animations, the roster and the
   footer hairline are tied to their own position in the viewport rather than to
   page load. Below the fold that is the difference between an animation the
   visitor actually sees and one that finished before they scrolled to it.

   Layered as an enhancement deliberately: `animation-timeline` is ignored by
   browsers that do not implement it, and the load-time declarations above still
   stand, so those visitors keep exactly the previous behaviour. No JavaScript
   either way — the one script the site ships drives the hero clips and nothing
   else, and these reveals do not depend on it.
   -------------------------------------------------------------------------- */

@supports (animation-timeline: view()) {
    .person {
        /* Duration is inert on a view timeline — progress is scroll position,
           not elapsed time — but has to stay non-zero for the animation to
           apply at all. The shorthand also clears the fallback stagger above,
           which would otherwise offset the scroll mapping. */
        animation: lsRise 1s var(--ease) both;
        animation-timeline: view();
        animation-range: entry 5% cover 24%;
    }

    .rule--footer {
        animation: lsRule 1s var(--ease) both;
        animation-timeline: view();
        animation-range: entry 0% entry 85%;
    }
}

/* --------------------------------------------------------------------------
   Narrow viewports

   The design source was desktop-only. Below 720px the portrait column and the
   bio cannot share a row, so the roster stacks.
   -------------------------------------------------------------------------- */

@media (max-width: 720px) {
    :root { --gutter: 24px; }

    .masthead {
        flex-direction: column;
        align-items: flex-start;
        gap: 22px;
        padding-top: 26px;
    }

    .hero { padding: 84px 0 88px; }
    .hero__subhead { font-size: 18px; }

    /* `cover` crops the skyline to a tall slice at this width, putting building
       detail directly behind the copy rather than below it. Shorten the band and
       strengthen the wash so the hero text keeps its contrast. */
    .hero-bleed { height: 600px; }

    .hero-bleed__wash {
        background: linear-gradient(
            to bottom,
            rgba(248, 246, 242, 0.76) 0%,
            rgba(248, 246, 242, 0.72) 30%,
            rgba(248, 246, 242, 0.94) 72%,
            var(--bg) 100%
        );
    }

    .page-head { padding: 64px 0 48px; }

    .person {
        grid-template-columns: minmax(0, 1fr);
        gap: 28px;
        padding: 40px 0;
    }

    .person__portrait { max-width: 260px; }

    .roster { padding-bottom: 88px; }
}
