/* ==========================================================================
   EO Robotics Lab — Editorial Layout Layer
   --------------------------------------------------------------------------
   Loaded LAST on every page, after the per-page stylesheets.

   Why this file exists
   --------------------
   The page stylesheets each grew their own "centred island" header: a title
   and a lead paragraph capped at 600–800px and centred inside a 1400px
   container. At desktop widths that leaves enormous empty gutters on both
   sides and makes pages read as unfinished.

   This layer replaces that pattern site-wide with an editorial split — the
   title anchors the left column, the lead sits in the right — so the full
   measure of the page is used. It also left-aligns card copy, which is what
   makes a grid feel deliberate rather than decorative.

   Centring is kept for one thing only: calls to action, where it reads as
   intentional.
   ========================================================================== */


/* --------------------------------------------------------------- container
   1400px was wider than the content ever filled. 1280 keeps generous margins
   without stranding the content in the middle of the screen. */
:root {
    --container-wide: 1280px;
    --container-max:  1120px;
}


/* -------------------------------------------------------- editorial header
   Title left, lead right, hairline underneath tying it to the content. */
.section-header,
.capabilities-header,
.process-header,
.technologies-header,
.internship-header,
.content-wrapper,
.culture-intro,
.career-intro,
.position-header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-6) var(--space-12);
    align-items: start;
    text-align: left;
    max-width: none;
    margin-left: 0;
    margin-right: 0;
    margin-bottom: var(--space-10);
    padding-bottom: var(--space-6);
    border-bottom: 1px solid var(--line-soft);
}

/* Headings and leads inside those headers stop being auto-centred blocks. */
.section-header > *,
.capabilities-header > *,
.process-header > *,
.technologies-header > *,
.internship-header > *,
.content-wrapper > *,
.culture-intro > *,
.career-intro > *,
.position-header > * {
    margin-left: 0;
    margin-right: 0;
    text-align: left;
}

.section-title,
.capabilities-header h2,
.process-header h2,
.technologies-header h2,
.internship-header h2,
.content-wrapper h2,
.culture-intro h2,
.career-intro h2 {
    margin-bottom: 0;
    max-width: 18ch;          /* keeps display type to two or three lines */
}

.section-subtitle,
.capabilities-header p,
.process-header p,
.technologies-header p,
.internship-header p,
.content-wrapper p,
.culture-intro p,
.career-intro p {
    margin: 0;
    max-width: var(--measure);
    /* Drop the lead's first line so it sits with the title's x-height rather
       than its cap — the two columns are very different type sizes. */
    padding-top: 0.35em;
    /* A second lead paragraph is still just one grid child among others, so
       plain auto-placement wrapped it back under the heading in column 1
       instead of stacking it under the first paragraph in column 2 — e.g.
       services/edu-tech.html's intro, which carries two. Pinning every <p>
       here to column 2 keeps them together as "the lead" regardless of how
       many there are; the existing p + p margin-top rules already space
       them apart once they share a column. */
    grid-column: 2;
}

/* A header carrying only a heading should not leave half the row empty. */
.section-header:not(:has(p)),
.capabilities-header:not(:has(p)),
.process-header:not(:has(p)),
.technologies-header:not(:has(p)) {
    grid-template-columns: 1fr;
}

@media (max-width: 900px) {
    .section-header,
    .capabilities-header,
    .process-header,
    .technologies-header,
    .internship-header,
    .content-wrapper,
    .culture-intro,
    .career-intro,
    .position-header {
        grid-template-columns: 1fr;
        gap: var(--space-4);
    }

    /* The leads above are pinned to `grid-column: 2` so multiple paragraphs
       stay together in the right-hand column on desktop. Collapsing the
       template to a single track does NOT undo that placement: the pinned
       paragraph then generates an *implicit* second column and the header
       stays two-up on a phone, which is what it was doing on every page
       carrying one of these headers. Hand the leads back to auto placement
       so they stack under the heading. */
    .section-header p,
    .capabilities-header p,
    .process-header p,
    .technologies-header p,
    .internship-header p,
    .content-wrapper p,
    .culture-intro p,
    .career-intro p,
    .position-header p {
        grid-column: auto;
        padding-top: 0;
    }

    .section-title,
    .capabilities-header h2,
    .process-header h2,
    .technologies-header h2,
    .internship-header h2,
    .content-wrapper h2,
    .culture-intro h2,
    .career-intro h2 {
        max-width: none;
    }
}


/* ------------------------------------------------------------- about block
   The section title now carries no subtitle of its own (it moved down into
   .about-feature, next to "Built in Lusaka" — see the comment there), so it
   reads as a standalone centred heading instead of half of a title/lead
   split. */
.about .section-header {
    text-align: center;
}

/* The photo straddles the seam by being pulled up 192px (.band-overlap in
   patterns.css), which swallows the band's whole top padding — the title then
   landed hard against the image's bottom edge. Hand the header back the air
   the negative margin took, on the same --section-y rhythm every other band
   opens with. */
.about .band-overlap + .section-header {
    margin-top: var(--section-y);
}

.about .section-header > * {
    text-align: center;
    margin-left: auto;
    margin-right: auto;
}

/* Two paragraphs side by side use the width instead of stacking into a
   narrow ribbon down the middle of the page. */
.about-content {
    max-width: none;
}

.about-text {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: var(--space-6) var(--space-12);
    max-width: none;
    margin: 0 0 var(--space-12);
}

.about-text p {
    margin: 0;
    max-width: var(--measure);
}


/* ------------------------------------------------------- standalone titles
   Sub-section headings that are not wrapped in a header block. They sit on
   the same left edge as everything else, with the same hairline. */
.values-section {
    text-align: left;
}

.values-section h3 {
    font-size: var(--text-4xl);
    letter-spacing: var(--tracking-tighter);
    margin-bottom: var(--space-8);
    padding-bottom: var(--space-6);
    border-bottom: 1px solid var(--line-soft);
}


/* ------------------------------------------------------------------- grids
   A hard 2-column grid capped at 1100px leaves short entries (contact
   details, capability blurbs) stranded beside empty half-rows. Let them pack
   to the available width instead. */
.features-grid {
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    max-width: none;
}

.capabilities-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--space-8) var(--space-10);
}


/* --------------------------------------------------------------- card copy
   Left-aligned card content reads as designed; centred card content reads as
   a template. */
.capability-item,
.benefit-card,
.process-step,
.culture-card,
.tech-item,
.value-card,
.mv-card,
.service-card-content {
    text-align: left;
}

.capability-item > *,
.benefit-card > *,
.process-step > *,
.culture-card > *,
.tech-item > * {
    margin-left: 0;
    margin-right: 0;
}


/* -------------------------------------------------------------------- CTAs
   The one place centring is kept — a call to action should sit on the axis. */
.cta-content,
.cta-section,
.apply-section {
    text-align: center;
}

.cta-content p,
.cta-section p,
.apply-section p {
    margin-left: auto;
    margin-right: auto;
    max-width: var(--measure-tight);
}


/* ==========================================================================
   MEDIA CARDS
   Service cards carry a photograph. A fixed aspect ratio keeps the grid
   even regardless of the source crop, and reserving the box prevents the
   layout shifting as images decode.
   ========================================================================== */

.service-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius-lg);
    transition: var(--transition-elevate), border-color var(--duration-base) var(--ease);
}

.service-card:hover {
    border-color: var(--line);
}

.service-card-media {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background: var(--surface-sunken);
}

.service-card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform var(--duration-slow) var(--ease);
}


.service-card-content {
    display: flex;
    flex-direction: column;
    flex: 1;
    gap: var(--space-3);
}

/* Push the link to the bottom so every card's CTA lines up. */
.service-card .service-link {
    margin-top: auto;
    padding-top: var(--space-2);
    font-weight: var(--weight-medium);
}

.service-card p {
    margin-bottom: 0;
}


/* ==========================================================================
   ABOUT — a photograph carries the section instead of type alone.
   ========================================================================== */

/* Used to be a photo+text grid, but the photo (.about-feature-media above)
   was pulled out to straddle the section seam instead, leaving this with a
   single text child — the two-column grid then shoved that child into the
   narrower column and left the other one empty. A plain measure-width block
   reads as one intentional paragraph instead of half a layout. */
.about-feature {
    max-width: 640px;
    margin: 0 auto var(--space-12);
    text-align: center;
}

.about-feature-media {
    position: relative;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    border-radius: var(--radius-xl);
    background: var(--surface-sunken);
}

.about-feature-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.about-feature-caption {
    margin-top: var(--space-3);
    font-size: var(--text-xs);
    color: var(--ink-faint);
}

/* ==========================================================================
   PARTNERS — third-party marks are credibility. Kept quiet by default so
   they support the page rather than competing with it.
   ========================================================================== */

.partners {
    padding: var(--section-y-tight) 0;
    border-top: 1px solid var(--line-soft);
}

.partners-label {
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-wide);
    text-transform: none;
    color: var(--ink-faint);
    margin-bottom: var(--space-6);
}

/* The window the line travels through. It was a wrapping grid, which broke
   the marks across two or three ragged rows on a narrow screen; now they run
   as one straight line that scrolls. Edges fade out so marks enter and leave
   rather than being cut off against the section's border. */
.partners-strip {
    overflow: hidden;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.partners-track {
    display: flex;
    align-items: center;
    /* Sized by content, not by the container — otherwise the flex row would
       compress every mark to fit instead of running off the edge. */
    width: max-content;
    /* Half the track is the duplicate set (index.html), so travelling exactly
       -50% lands the copy precisely where the original started. */
    animation: partnersScroll 45s linear infinite;
}

/* Marks vary wildly in aspect ratio — Raspberry Pi's is portrait — so they
   are held to a common height and allowed their natural width, rather than
   being boxed to a fixed width which shrank the tall ones to a sliver. */
.partners-track img {
    flex: none;
    height: 56px;
    width: auto;
    max-width: 150px;
    object-fit: contain;
    /* The spacing is a trailing margin per mark rather than `gap` on the
       track. With `gap`, 18 marks have only 17 gaps — the last one carries
       none — so the two halves are not equal and -50% lands half a gap
       short, which shows as a stutter each time the loop restarts. A margin
       on every mark makes each one occupy width + spacing, so the track is
       exactly two equal halves and the loop is seamless. */
    margin-right: var(--space-10);
    /* Shown at full fidelity. A grayscale + reduced-opacity treatment is the
       usual convention, but several of these marks are light artwork and
       washed out to nothing against the white section. */
    opacity: 0.9;
    transition: opacity var(--duration-base) var(--ease);
}

@keyframes partnersScroll {
    to { transform: translateX(-50%); }
}

/* Let a reader stop the line to look at a mark. Pointer devices only: on a
   phone this would fire on the tap that is trying to scroll. */
@media (hover: hover) {
    .partners-strip:hover .partners-track {
        animation-play-state: paused;
    }

    .partners-track img:hover {
        opacity: 1;
    }
}

@media (max-width: 640px) {
    .partners-track {
        animation-duration: 30s;
    }

    .partners-track img {
        height: 44px;
        max-width: 120px;
        margin-right: var(--space-8);
    }
}

/* Motion off: stop the travel and hand the line to the reader to swipe. */
@media (prefers-reduced-motion: reduce) {
    .partners-strip {
        overflow-x: auto;
    }

    .partners-track {
        animation: none;
    }
}


/* ==========================================================================
   SCROLL REVEAL
   Content arrives as it enters the viewport. The .reveal class is added by
   script.js only when IntersectionObserver is available, so with JS disabled
   everything stays visible. The reduced-motion override in tokens.css
   collapses the transition for anyone who has asked for that.
   ========================================================================== */

.reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity var(--duration-slow) var(--ease),
                transform var(--duration-slow) var(--ease);
    will-change: opacity, transform;
}

.reveal.is-visible {
    opacity: 1;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    .reveal { opacity: 1; transform: none; }
}


/* ==========================================================================
   SERVICE SHOWCASE
   --------------------------------------------------------------------------
   Alternating full-width rows rather than a card grid. Each service gets a
   large photograph on one side and its copy on the other, flipping direction
   down the page. Borrowed from how hardware companies present a product line
   — it gives each service real presence instead of reducing it to a tile —
   but kept on the Apple palette: light ground, dark type, one accent.
   ========================================================================== */

.service-rows {
    display: flex;
    flex-direction: column;
    gap: var(--section-y-tight);
}

.showcase-row {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: clamp(var(--space-8), 5vw, var(--space-20));
    align-items: center;
}

/* Flip every other row so the eye zig-zags down the page. */
.showcase-row:nth-child(even) .showcase-media {
    order: 2;
}

.showcase-media {
    position: relative;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    border-radius: var(--radius-xl);
    background: var(--surface-sunken);
}

.showcase-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform var(--duration-slower) var(--ease);
}


.showcase-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
}

.showcase-body h3 {
    font-size: var(--text-display-sm);
    letter-spacing: var(--tracking-tighter);
    line-height: var(--leading-tight);
    margin: 0;
    max-width: 14ch;
}

.showcase-copy {
    font-size: var(--text-lg);
    color: var(--ink-soft);
    line-height: var(--leading-relaxed);
    max-width: var(--measure);
    margin: 0;
}

/* Direct CTA language, underline that draws on hover. */
.showcase-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-3);
    padding-bottom: 2px;
    color: var(--brand);
    font-weight: var(--weight-medium);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: color var(--duration-base) var(--ease),
                border-color var(--duration-base) var(--ease);
}

.showcase-link:hover {
    color: var(--brand-dark);
    border-bottom-color: currentColor;
}

.showcase-link span {
    transition: transform var(--duration-base) var(--ease);
}

.showcase-link:hover span {
    transform: translateX(3px);
}

@media (max-width: 900px) {
    .showcase-row {
        grid-template-columns: 1fr;
        gap: var(--space-5);
    }

    /* Media always leads on narrow screens; the zig-zag has no meaning in
       a single column and would just scramble the reading order. */
    .showcase-row:nth-child(even) .showcase-media {
        order: 0;
    }

    .showcase-body h3 {
        max-width: none;
    }
}


/* Showcase row used inside a service page sits in its own band. */
.service-feature {
    padding: var(--section-y) 0;
}


/* ==========================================================================
   SERVICE HERO WITH VIDEO
   --------------------------------------------------------------------------
   Opt in by adding .service-hero--video and dropping a .hero-video-background
   inside, same markup as the homepage. The dark gradient already on
   .service-hero stays behind the video, so the hero is never bare while the
   clip loads or if it fails to decode.
   ========================================================================== */

.service-hero--video .hero-video-background {
    z-index: 0;
}

/* The heading is white and these clips are bright, so the scrim goes deeper
   than the homepage's flat 0.4. */
.service-hero--video .hero-overlay {
    background: linear-gradient(180deg,
                rgba(0, 0, 0, 0.52) 0%,
                rgba(0, 0, 0, 0.60) 55%,
                rgba(0, 0, 0, 0.72) 100%);
}

/* The animated tint and film-grain layers were tuned against a flat
   gradient; over moving footage they just read as dirt. */
.service-hero--video::before { opacity: 0.22; animation: none; }
.service-hero--video::after  { opacity: 0.15; }

.service-hero--video .container { z-index: 2; }


/* ==========================================================================
   INSTANT PRINT QUOTE
   --------------------------------------------------------------------------
   Two columns: the pitch on the left, the drop target on the right. The
   original was a centred 640px header above a centred 720px box, which left
   the section reading as an island. Rebuilt on tokens — the original used
   raw values and a second blue (#1478a8) that is not the brand colour.
   ========================================================================== */

.print-quote-section {
    padding: var(--section-y) 0;
    background: var(--surface-sunken);
    border-bottom: 1px solid var(--line-soft);
}

.quote-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(var(--space-8), 5vw, var(--space-16));
    align-items: center;
}

.quote-intro {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
}

.quote-header,
.quote-intro h2 {
    margin: 0;
    max-width: none;
    text-align: left;
}

.quote-intro h2 {
    font-size: var(--text-display-sm);
    letter-spacing: var(--tracking-tighter);
    line-height: var(--leading-tight);
}

.quote-lead {
    font-size: var(--text-lg);
    color: var(--ink-soft);
    line-height: var(--leading-relaxed);
    max-width: var(--measure);
    margin: 0;
}

.quote-points {
    list-style: none;
    padding: 0;
    margin: var(--space-3) 0 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.quote-points li {
    position: relative;
    padding-left: var(--space-6);
    font-size: var(--text-sm);
    color: var(--ink-soft);
    line-height: var(--leading-normal);
}

.quote-points li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.55em;
    width: 6px;
    height: 6px;
    border-radius: var(--radius-full);
    background: var(--brand);
}

/* ---------------------------------------------------------------- dropzone */

.quote-dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    background: var(--surface);
    border: 2px dashed var(--line);
    border-radius: 0;
    padding: clamp(var(--space-8), 4vw, var(--space-12)) var(--space-6);
    text-align: center;
    max-width: none;
    margin: 0;
    box-shadow: var(--shadow-sm);
    transition: border-color var(--duration-base) var(--ease),
                background-color var(--duration-base) var(--ease),
                box-shadow var(--duration-base) var(--ease);
}

.quote-dropzone.dragover {
    border-color: var(--brand);
    border-style: solid;
    background: var(--brand-tint);
    box-shadow: var(--shadow-md);
}

.quote-dropzone.busy {
    opacity: 0.65;
    pointer-events: none;
}

.quote-dropzone i {
    font-size: var(--text-5xl);
    color: var(--brand);
    margin: 0;
}

.quote-dropzone h3 {
    font-size: var(--text-xl);
    margin: 0;
    letter-spacing: var(--tracking-tight);
}

.quote-or {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--ink-faint);
    margin: 0;
}

.quote-dropzone .quote-hint {
    display: block;
    margin-top: var(--space-4);
    font-size: var(--text-xs);
    color: var(--ink-faint);
}

.quote-errors:empty {
    display: none;
}

.quote-errors {
    max-width: var(--measure);
    margin: var(--space-6) 0 0;
    padding: var(--space-4) var(--space-5);
    background: var(--danger-bg);
    border: 1px solid var(--danger);
    border-radius: var(--radius-md);
    color: var(--danger);
    font-size: var(--text-sm);
}

@media (max-width: 900px) {
    .quote-layout {
        grid-template-columns: 1fr;
        gap: var(--space-6);
    }
}
