/*
 * StoreForge Pro — site.css
 *
 * Marketing-site styles that sit on top of the generated Tailwind utility
 * sheet. Kept intentionally small: brand-tinted decorations, a couple of
 * animations, prose defaults for long-form pages, and print tidy-ups.
 * The brand custom properties (--sf-brand etc.) are set inline in header.php.
 */

/* Skip-to-content link: visually hidden until focused --------------------- */

.sf-skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    padding: .6rem 1rem;
    border-radius: .6rem;
    background: #fff;
    color: var(--sf-brand-dark, #15803d);
    font-weight: 600;
    box-shadow: 0 10px 30px -10px rgba(15, 23, 42, .4);
}
.sf-skip-link:focus {
    left: .75rem;
    top: .75rem;
}

/* ---- Brand-aware helpers ------------------------------------------------- */

.text-brand      { color: var(--sf-brand); }
.text-brand-dark { color: var(--sf-brand-dark); }
.bg-brand        { background-color: var(--sf-brand); }
.bg-brand-dark   { background-color: var(--sf-brand-dark); }
.bg-brand-light  { background-color: var(--sf-brand-light); }
.border-brand    { border-color: var(--sf-brand); }
.ring-brand      { --tw-ring-color: var(--sf-brand); }

/* Hero and section backgrounds ------------------------------------------- */

.sf-hero-glow {
    position: relative;
    overflow: hidden;
    isolation: isolate;
}
.sf-hero-glow::before {
    content: "";
    position: absolute;
    inset: -20% 30% auto -10%;
    height: 480px;
    background: radial-gradient(closest-side, color-mix(in srgb, var(--sf-brand) 22%, transparent), transparent);
    z-index: -1;
    pointer-events: none;
}
.sf-hero-glow::after {
    content: "";
    position: absolute;
    inset: auto -15% -30% 40%;
    height: 420px;
    background: radial-gradient(closest-side, color-mix(in srgb, var(--sf-accent) 18%, transparent), transparent);
    z-index: -1;
    pointer-events: none;
}

.sf-grid-bg {
    background-image:
        linear-gradient(to right, rgba(15, 23, 42, .04) 1px, transparent 1px),
        linear-gradient(to bottom, rgba(15, 23, 42, .04) 1px, transparent 1px);
    background-size: 40px 40px;
    -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 55%, transparent 100%);
            mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 55%, transparent 100%);
}

.sf-brand-gradient {
    background-image: linear-gradient(135deg, var(--sf-brand), var(--sf-accent));
}

/* A button that reads well sitting on top of the brand gradient. */
.sf-btn-on-brand {
    background: rgba(255, 255, 255, .12);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, .5);
}
.sf-btn-on-brand:hover {
    background: rgba(255, 255, 255, .2);
}

.sf-text-gradient {
    background-image: linear-gradient(120deg, var(--sf-brand-dark), var(--sf-accent));
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
}

/* Homepage hero, browser mock and theme showcase --------------------------
   The hero band on index.php — copy one side, a browser mockup the other — the
   four-step row under it and the theme showcase below that. Three files write
   that markup: index.php, plus the two partials it pulls in behind is_file():
   includes/partials/hero-visual.php (the window, a phone, the cards floated
   beside them, a compact four-step row) and theme-showcase.php (the theme cards
   and a device stage). All three build their rows out of grid utilities and
   leave the polish here, so this is where a shadow, a glow, a fluid padding or
   a breakpoint of our own lives. .sf-hero-glow above still paints the two brand
   washes behind the band; nothing here draws a page background.

   Written to survive markup that is being written in parallel with it:

   * Hook classes only, never a nesting depth. Where a rule has to know that
     something is a direct child it says so with :has(> …) — which means it does
     nothing at all when it is not, and all three files put .sf-hero on a
     <section> and .sf-showcase on a <section>. Those two rules sleep there and
     the rules that dress the parts do the work.
   * Two hooks carry two scales: .sf-mock-card is a tile inside the window and a
     card floated over its edge, .sf-steps is a four-card row on the page and a
     compact one inside the drawing. Each gets only what is true at both, and
     what is true of one is scoped by where it sits (.sf-mock-screen
     .sf-mock-card) instead of being asserted for both.
   * site.css loads after tailwind.css, so a declaration here beats the same
     declaration from a utility of equal specificity. That is how a fixed p-5
     becomes fluid padding, and how a phone width puts a floated card back into
     the flow, with no !important anywhere.
   * Nothing here scales the drawing with a transform, which would take its type
     down with it. The chrome, the padding and the tiles are clamp()ed instead,
     so the window narrows while its text keeps a size worth reading.
   * No z-index and no placement for anything the drawing floats. Those pieces
     are positioned against hero-visual.php's own stage and stack in document
     order; a z-index here would put the window in front of them. */
/* overflow-x agrees with the overflow:hidden that .sf-hero-glow already puts on
   this element in index.php, and is here for a hero band that does not carry
   that class: a card floated a few pixels past the edge can then never hand the
   page a horizontal scrollbar. clip rather than hidden, so no scroll container
   is created and the vertical axis is left alone. */
.sf-hero {
    position: relative;
    overflow-x: clip;
}

/* The two columns. order pins the copy first whichever order the markup puts
   them in — stacked, that means the headline and its buttons at the top of the
   page rather than under a decoration — and min-width lets a grid or flex track
   shrink below its contents instead of widening the page. */
.sf-hero-copy   { order: -1; min-width: 0; }
.sf-hero-visual { position: relative; min-width: 0; }

/* When the hook lands on the row itself rather than on the band it can own the
   collapse as well: one column, then two side by side in the 1024px block. The
   flex bases are inert wherever .sf-hero is not a flex container, so they are
   safe to state unconditionally. */
.sf-hero:has(> .sf-hero-copy) {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: clamp(2rem, 6vw, 3rem);
}
.sf-hero > .sf-hero-copy   { flex: 1 1 28rem; }
.sf-hero > .sf-hero-visual { flex: 1 1 24rem; }

/* A headline with one word on the last line is the one thing on this page that
   reads as cheap. Ignored where text-wrap is not understood. */
.sf-hero-copy h1,
.sf-hero-copy h2 { text-wrap: balance; }

/* One call to action per row until there is room for two abreast: on a phone the
   primary action should be impossible to miss or to mis-tap. .sf-btn-block asks
   for the full width in its own right, so it keeps it past that point. */
.sf-hero-copy .sf-btn { width: 100%; }

/* Those buttons sit on a brand-tinted wash, where the brand-coloured ring the
   preflight gives every :focus-visible can sink into the background. Same ring
   in the darker brand ink, one more pixel of offset — never none. */
.sf-hero-copy a:focus-visible,
.sf-hero-copy button:focus-visible {
    outline: 2px solid var(--sf-brand-dark);
    outline-offset: 3px;
}
/* The browser window. Its radius is a local custom property because three rules
   have to agree on it, and both panes below round their own outer corners rather
   than trusting the frame to clip them — a frame with a phone or a card over its
   edge needs to be free not to clip. The calc() fallbacks keep a pane's corners
   right if one is ever drawn outside a frame. The shadow is two layers: a
   hairline that reads as the screen's edge and a wide, low-opacity cast that
   only shows as depth. */
.sf-mock {
    --sf-mock-radius: 1rem;
    position: relative;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    border: 1px solid var(--sf-line);
    border-radius: var(--sf-mock-radius);
    background-color: var(--sf-surface);
    font-size: clamp(.8125rem, 2.6vw, .9375rem);
    box-shadow: 0 1px 2px rgba(15, 23, 42, .06),
                0 30px 60px -34px rgba(15, 23, 42, .45);
}

/* height:auto so a taller row of chrome than the markup's h-9 allows for cannot
   be clipped; min-height so the bar cannot collapse without one. */
.sf-mock-bar {
    display: flex;
    align-items: center;
    gap: .4rem;
    height: auto;
    min-height: clamp(2.05rem, 6.5vw, 2.25rem);
    padding-inline: clamp(.55rem, 2.4vw, .8rem);
    border-bottom: 1px solid var(--sf-line);
    border-radius: calc(var(--sf-mock-radius, 1rem) - 1px)
                   calc(var(--sf-mock-radius, 1rem) - 1px) 0 0;
    background-color: var(--sf-subtle);
    color: #64748b;
    font-size: .75rem;
}

/* The storefront pane. Fluid padding is most of what keeps a three-across row of
   tiles inside a 320px column: at that width it gives back nine pixels a fixed
   p-5 would have spent on air. */
.sf-mock-screen {
    min-width: 0;
    padding: clamp(.65rem, 3.2vw, 1.25rem);
    border-radius: 0 0 calc(var(--sf-mock-radius, 1rem) - 1px)
                         calc(var(--sf-mock-radius, 1rem) - 1px);
    background-color: var(--sf-surface);
}
/* The phone. Bezel only: a dark body, a tight pad, a deep cast and a corner
   radius, so the partial has to fill it and nothing else. Its placement and
   whether it shows at a given width are the drawing's own — hero-visual.php
   floats it in a gutter it reserves with sm: utilities — so no position, no
   inset, no width and no z-index is stated here. What is stated is the phone
   width, further down: at 320px there is no gutter to float into. */
.sf-mock-phone {
    overflow: hidden;
    max-width: 100%;
    padding: .3rem;
    border-radius: 1.4rem;
    background-color: var(--sf-ink);
    box-shadow: 0 20px 40px -20px rgba(15, 23, 42, .6);
}

/* A card in the drawing, at either of its two scales: a tile inside the window,
   or a theme mock floated over the frame's edge. Only what is true of both is
   here. Colour is not: a tile inside the window belongs to whichever theme the
   window is previewing, and hero-visual.php paints those from the palette it
   read, with inline styles that outrank anything in this file — which is the
   right way round, because a neutral hairline would be wrong inside a dark
   theme's storefront. Placement is not either; it is the composition, and the
   composition is the markup's. */
.sf-mock-card {
    min-width: 0;
    max-width: 100%;
    border-radius: .75rem;
}
/* Inside the window, though, the padding is ours, and it is what lets three
   tiles across survive a 320px column: six pixels there against a fixed twelve,
   with the type left alone at a size worth reading. Scoped by where the tile
   sits, so a card floated outside the window keeps its own. */
.sf-mock-screen .sf-mock-card {
    padding: clamp(.35rem, 1.9vw, .7rem);
    font-size: .8125rem;
}
/* The theme showcase. Both files that draw it put .sf-showcase on a <section>
   and build the card grid inside it — theme-showcase.php with
   "grid gap-6 sm:grid-cols-2 lg:grid-cols-3", which is already the three across
   a desktop, two a tablet and one a phone this section wants — so the grid here
   is stated only for the case where the cards are the hook's own children, and
   is inert in both of those files. A card looks the same in either. */
.sf-showcase:has(> .sf-showcase-card) {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(1rem, 2.6vw, 1.5rem);
}

/* The card. Polish only — the card body itself is .sf-card or the markup's own
   border and fill. overflow keeps a theme's storefront picture inside the corner
   radius; the lift and the brand-tinted edge are the whole hover. transform is
   ours by arrangement: hero-visual.php leaves these unrotated precisely so two
   files cannot fight over one property, and this file adds no rotation either,
   since the same hook dresses the six full-size cards in the showcase. */
.sf-showcase-card {
    position: relative;
    min-width: 0;
    overflow: hidden;
    transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.sf-showcase-card:hover {
    transform: translateY(-4px);
    border-color: color-mix(in srgb, var(--sf-brand) 35%, var(--sf-line));
    box-shadow: 0 24px 45px -28px rgba(15, 23, 42, .45);
}
/* A card that is itself the link wears the ring: its own overflow would crop an
   outline drawn on a child, and there is nothing else in the card to draw it on. */
.sf-showcase-card:focus-visible {
    outline: 2px solid var(--sf-brand);
    outline-offset: 2px;
    transform: translateY(-4px);
}
/* A card that only contains the links takes the hover treatment instead of a
   second ring: the buttons inside are .sf-btn and ring themselves, and two rings
   inside one another read as an error. Its own rule, so an engine without :has()
   drops this and keeps the case above. */
.sf-showcase-card:has(:focus-visible) {
    transform: translateY(-4px);
    border-color: color-mix(in srgb, var(--sf-brand) 35%, var(--sf-line));
    box-shadow: 0 24px 45px -28px rgba(15, 23, 42, .45);
}

/* One slow drift, used by the phone at desktop widths. translate rather than
   transform so it composes with a rotation the markup may already be applying
   instead of replacing it. Kept beside its user, as sf-video-pulse is. */
@keyframes sf-float {
    0%, 100% { translate: 0 0; }
    50%      { translate: 0 -8px; }
}
@media (min-width: 640px) {
    /* Room for two buttons abreast, so they stop asking for the whole row. */
    .sf-hero-copy .sf-btn:not(.sf-btn-block) { width: auto; }

    .sf-showcase:has(> .sf-showcase-card) {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1024px) {
    /* Two columns, the copy on the wider track: a headline wants the room, and
       the drawing beside it reads better narrow than the copy does. */
    .sf-hero:has(> .sf-hero-copy) {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        gap: clamp(2.5rem, 5vw, 4.5rem);
    }

    /* A brand halo under the drawing. On a pseudo-element, so no partial has to
       carry a spare div; on the column rather than on the window, so it is not
       painted out by the window's own white. z-index:-1 puts it behind both
       inside the stacking context .sf-hero-glow already isolates, and an engine
       without color-mix() drops the tint and keeps the rest of the page. */
    .sf-hero-visual::before {
        content: "";
        position: absolute;
        inset: 8% -4% -10% -4%;
        z-index: -1;
        background: radial-gradient(closest-side,
            color-mix(in srgb, var(--sf-brand) 20%, transparent), transparent);
        pointer-events: none;
    }

    /* Depth, at the width where there is room to read it. Same two layers, the
       cast thrown further: still no sideways spread, so nothing is clipped. */
    .sf-mock {
        box-shadow: 0 1px 2px rgba(15, 23, 42, .06),
                    0 44px 80px -40px rgba(15, 23, 42, .5);
    }

    /* Eight pixels, seven seconds, from the width where the phone has a gutter
       of its own to drift in. Switched off below with the rest of the motion. */
    .sf-mock-phone {
        animation: sf-float 7s ease-in-out infinite;
    }

    .sf-showcase:has(> .sf-showcase-card) {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}
/* The one max-width query in this file, and it has to be one: a card is floated
   over the window by a utility on the card itself, so a phone width has to take
   that away rather than a wide width add it. Two floated cards on a 360px screen
   land on each other, and a negative inset there pushes the whole page sideways.
   A no-op for a tile in the window's own grid, which is static and auto-inset
   already, and it agrees with hero-visual.php, which reserves no gutter and
   floats nothing at all below this width. */
@media (max-width: 639.98px) {
    .sf-mock-card {
        position: static;
        inset: auto;
        width: auto;
        max-width: 100%;
        animation: none;
        translate: none;
    }
    /* Squeezed into a 320px column the phone would say less than the window
       beside it says already, and still cost a paint. Restacked rather than
       shrunk: the window keeps the width, and the steps under it keep the rest. */
    .sf-mock-phone { display: none; }
}

/* Every move this section adds, in one place a reader can switch off. The global
   block in tailwind.css already cuts every duration to a thousandth of a
   millisecond; this takes the drift and the lift away outright, and names the
   hover and focus states because a class with :hover on it outranks the class on
   its own. The focus ring is untouched — it is not decoration. */
@media (prefers-reduced-motion: reduce) {
    .sf-mock-phone {
        animation: none;
        translate: none;
    }
    .sf-showcase-card { transition: none; }
    .sf-showcase-card:hover,
    .sf-showcase-card:focus-visible { transform: none; }
    .sf-showcase-card:has(:focus-visible) { transform: none; }
}

/* On paper the drawing is a picture of a website inside a page about building
   websites: keep the window, drop the ink around it. Decided here rather than by
   whether a min-width query happens to match the paper, which is the reasoning
   the print block at the foot of this file sets out. */
@media print {
    .sf-hero-visual::before { display: none; }
    .sf-mock { box-shadow: none; }
    .sf-mock-phone { display: none; }
    .sf-showcase-card { box-shadow: none; }
}

/* Decorative check bullets ------------------------------------------------ */

.sf-checklist { list-style: none; padding: 0; margin: 0; }
.sf-checklist li {
    position: relative;
    padding-left: 1.9rem;
    margin-bottom: .6rem;
    line-height: 1.5;
}
.sf-checklist li::before {
    content: "";
    position: absolute;
    left: 0;
    top: .15rem;
    width: 1.25rem;
    height: 1.25rem;
    border-radius: 9999px;
    background: var(--sf-brand-light);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%2315803d' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
}

/* Long-form prose (about / how-it-works copy) ----------------------------- */

.sf-prose { color: #334155; line-height: 1.7; }
.sf-prose > * + * { margin-top: 1.1em; }
.sf-prose h2 { font-size: 1.6rem; font-weight: 800; color: #0f172a; margin-top: 2.2em; letter-spacing: -.01em; }
.sf-prose h3 { font-size: 1.2rem; font-weight: 700; color: #0f172a; margin-top: 1.8em; }
.sf-prose p  { margin-top: 1em; }
.sf-prose ul { padding-left: 1.3em; list-style: disc; }
.sf-prose ol { padding-left: 1.3em; list-style: decimal; }
.sf-prose li { margin-top: .4em; }
.sf-prose a  { color: var(--sf-brand-dark); text-decoration: underline; text-underline-offset: 2px; }
.sf-prose strong { color: #0f172a; }

/* Step connector for "how it works" -------------------------------------- */

.sf-steps { counter-reset: sf-step; }
.sf-step-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 9999px;
    font-weight: 800;
    color: #fff;
    background-image: linear-gradient(135deg, var(--sf-brand), var(--sf-accent));
}

/* The numbered step row, at the scales it is drawn at: index.php's five cards
   across the page, and the compact row inside hero-visual.php's drawing. Both
   build the row out of grid utilities at the size they need — 1/2/5 across on
   the page — so nothing here states a column count or a gap, which would force
   one of those scales onto the other. What is left is what is true at both: a
   flex row wraps rather than squashing, and its cards are all as tall as the
   tallest. */
.sf-steps {
    flex-wrap: wrap;
    align-items: stretch;
    min-width: 0;
}
.sf-step {
    position: relative;
    min-width: 0;
}

/* 01 → 05. From the width where the whole row stands on one line, a hairline
   leaves each card and fades out towards the next, so the row reads as a
   sequence and not as unrelated cards. It is drawn in the gap, and given no more
   length than the narrowest gap it may have to cross: any excess is already
   fading out, and is painted under the next card, which the rule above positions
   and which therefore paints after it. The last card of a row has nothing to
   point at.

   nth-child(4n) is the end of a line only while the row is four wide — the shape
   this block was first written for. A five-wide row is declared by .is-count-5 on
   the list, and there the fourth card is mid-sequence and keeps its hairline;
   only :last-child stops. Any future width states its own count the same way
   rather than editing the rule above. */
@media (min-width: 1024px) {
    .sf-step::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 100%;
        width: clamp(.75rem, 1.5vw, 1.5rem);
        height: 1px;
        background-image: linear-gradient(to right, var(--sf-line), transparent);
        background-image: linear-gradient(to right,
            color-mix(in srgb, var(--sf-brand) 45%, var(--sf-line)), transparent);
        pointer-events: none;
    }
    .sf-step:nth-child(4n)::after,
    .sf-step:last-child::after { content: none; }
    .sf-steps.is-count-5 > .sf-step:nth-child(4n)::after { content: ""; }
    .sf-steps.is-count-5 > .sf-step:last-child::after { content: none; }
}

/* Animations -------------------------------------------------------------- */

@keyframes sf-fade-up {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: none; }
}
.sf-animate-in { animation: sf-fade-up .5s ease both; }
.sf-animate-in-2 { animation: sf-fade-up .5s ease .08s both; }

@media (prefers-reduced-motion: reduce) {
    .sf-animate-in, .sf-animate-in-2 { animation: none; }
    * { scroll-behavior: auto !important; }
}

html { scroll-behavior: smooth; }

/* Rotate an element when its parent <details> is open (FAQ chevrons). */
details[open] .sf-open-rotate { transform: rotate(180deg); }
/* Hide the default disclosure triangle when we supply our own marker. */
summary.sf-summary::-webkit-details-marker { display: none; }
summary.sf-summary { list-style: none; }

/* Dark-surface details ----------------------------------------------------
   Tailwind's white-opacity border utilities are not part of the vendored
   build, and a tab strip needs to sit *on* the rule below it. Three tiny
   rules cover every case instead of bloating the generated stylesheet. */

.sf-line-light  { border-color: rgba(255, 255, 255, 0.12); }
.sf-tabs-bleed  { margin-left: -0.25rem; margin-right: -0.25rem; }
.sf-tab-seam    { margin-bottom: -1px; }

/* Hover states for the white-alpha backgrounds -----------------------------
   gen_tailwind.py emits every plain .bg-white/NN step, but its state pass
   gates colour variants on INTERACTIVE_COLORS — a table of bare tokens
   (bg-white, bg-slate-100, bg-brand...) with no alpha steps in it — so
   hover:bg-white/10 and hover:bg-white/20 are never generated, and writing
   them into tailwind.css would only get them deleted on the next run. Two
   controls in dash-header.php need them and cannot take .sf-nav-link instead:
   the sidebar's close button is a fixed size-9 icon box that .sf-nav-link's
   padding and nav typography would break, and the "New store" button already
   rests at bg-white/10, so .sf-nav-link:hover's rgba(255,255,255,.08) would
   make it *darker* on hover and would strip its font-semibold text-white.
   Shape, specificity and values copied from the generated rules. */

.hover\:bg-white\/10:hover{background-color:rgba(255,255,255,0.1)}
.hover\:bg-white\/20:hover{background-color:rgba(255,255,255,0.2)}

/* Toast tone: info --------------------------------------------------------
   saas.js builds 'sf-toast-' + (tone || 'info'), so info is what a toast gets
   when the caller names no tone — the commonest case. Its siblings
   sf-toast-success and sf-toast-error live in the generated tailwind.css,
   which gen_tailwind.py rewrites from scratch, so this one lives here
   instead. Blue-700, the weight those two use. Do not tidy it back. */

.sf-toast-info { background-color: #1d4ed8; }

/* Preview chrome ----------------------------------------------------------
   preview.php is the only full-height page in the SaaS: a slim toolbar with
   the storefront in an iframe below it, filling whatever is left. The device
   buttons set frame.style.maxWidth (see saas.js), so the frame only needs to
   stay centred and animate the change. */

.sf-preview-shell {
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
}
.sf-preview-stage {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0.75rem;
    background-color: #e2e8f0;
}
.sf-preview-frame {
    display: block;
    width: 100%;
    height: 100%;
    margin-left: auto;
    margin-right: auto;
    border: 0;
    border-radius: 0.75rem;
    background-color: #fff;
    box-shadow: 0 18px 45px -25px rgba(15, 23, 42, .45);
    transition: max-width .25s ease;
}
[data-sf-preview-size].is-active {
    background-color: var(--sf-brand);
    border-color: var(--sf-brand);
    color: #fff;
}

/* Video frame -------------------------------------------------------------
   The empty state drawn by includes/partials/video-frame.php. Layout is
   Tailwind (aspect-video, absolute inset-0, flex, gap), so only what a utility
   cannot say lives here: the player gradient, the 16:9 fallback, the pulse and
   the Bangla tracking reset. store-template/SETUP-GUIDE.html paints the same
   placeholder in its own <style> for readers with no PHP — keep the two in
   step if either changes. */

/* Reset for prose contexts: <figure> loses its UA margin in preflight, but
   .sf-prose adds its own to every child. */
.sf-video-frame { margin: 0; }

.sf-video-stage {
    position: relative;
    overflow: hidden;
    border-radius: 1rem;
    background-color: #0b1220;
    background-image: linear-gradient(165deg, #16233c 0%, #0d1526 48%, #060a13 100%);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .09),
                0 22px 50px -30px rgba(15, 23, 42, .55);
}
/* Brand-tinted glow across the top of the stage, layered over that gradient.
   Kept as its own rule so an engine without color-mix() drops only the tint and
   still gets a graded player surface rather than a flat black box. */
.sf-video-stage {
    background-image:
        radial-gradient(ellipse 130% 90% at 50% -12%,
            color-mix(in srgb, var(--sf-brand) 20%, transparent), transparent 62%),
        linear-gradient(165deg, #16233c 0%, #0d1526 48%, #060a13 100%);
}
/* 16:9 where aspect-ratio is not understood. Harmless where .aspect-video does
   work: both ask for the same height, and everything on the stage is
   positioned, so the pseudo-element never displaces it. */
.sf-video-stage::before { content: ""; display: block; padding-bottom: 56.25%; }

.sf-video-play {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 4.5rem;
    height: 4.5rem;
    width: clamp(4rem, 11vw, 4.75rem);   /* 64px on a 360px phone, 76px past ~690px */
    height: clamp(4rem, 11vw, 4.75rem);
    border-radius: 9999px;
    color: #fff;
    background-color: rgba(255, 255, 255, .15);
    border: 1px solid rgba(255, 255, 255, .38);
    box-shadow: 0 16px 34px -18px rgba(0, 0, 0, .85);
}

/* One slow ring every three seconds: enough to read as a player, not enough to
   pull the eye off the copy. Drawn on a pseudo-element so the decoration never
   appears in the markup. */
.sf-video-play::after {
    content: "";
    position: absolute;
    inset: -7px;
    border-radius: 9999px;
    border: 1px solid rgba(255, 255, 255, .3);
    animation: sf-video-pulse 3s ease-out infinite;
}
@keyframes sf-video-pulse {
    0%        { opacity: .5; transform: scale(.95); }
    70%, 100% { opacity: 0;  transform: scale(1.2); }
}

.sf-video-caption {
    color: #e2e8f0;
    letter-spacing: .01em;
    text-shadow: 0 1px 14px rgba(0, 0, 0, .6);
}
/* Bangla is a conjunct script carrying marks above and below the line: the
   tracking that flatters a Latin line only prises its ligatures apart, and the
   caption wants the extra leading. Same reasoning as the [lang="bn"] rules in
   store-template/SETUP-GUIDE.html. */
.sf-video-caption:lang(bn) { letter-spacing: normal; line-height: 1.6; }

@media (prefers-reduced-motion: reduce) {
    .sf-video-play::after { animation: none; opacity: .3; transform: none; }
}

/* On paper a 16:9 slab of near-black is a page of toner for no information, so
   the stage inverts and the caption keeps carrying the message. */
@media print {
    .sf-video-stage { background: #f1f5f9; box-shadow: inset 0 0 0 1px #cbd5e1; }
    .sf-video-play { background-color: #e2e8f0; border-color: #cbd5e1; color: #334155; }
    .sf-video-play::after { display: none; }
    .sf-video-caption { color: #334155; text-shadow: none; }
}

/* Print -------------------------------------------------------------------
   Named by class, the way the two sibling sheets do it: store.css hides
   [data-sft-nav], [data-sft-drawer], [data-sft-scrim]; the generated store's
   admin.css hides .sfa-top, .sfa-side, .sfa-foot, .sfa-btnrow, .sfa-veil.
   On this side that is the admin sidebar and its off-canvas veil, the fixed
   toast stack (saas.js builds it at z-index 60 and nothing else hid it), and
   the skip link, which is only parked off-page by a -9999px offset. The two
   content columns also lose their sidebar gutter: .sf-main's margin and the
   customer column's lg:pl-64 both hang off a min-width:1024px query, and the
   point of this block is that print behaviour is decided here rather than by
   whether that query happens to match the paper.

   .sf-no-print carries what a class cannot reach — see dash-header.php (the
   customer sidebar, the store editor's tab strip) and both shells' flash
   dismiss buttons. .sf-btn is deliberately NOT hidden: it is the button
   component itself rather than a row wrapper like .sfa-btnrow, and some of
   its labels are the only place a fact appears on the page ("Current theme",
   "Not in your plan" in dashboard/theme.php, "Selected" in submit.php). */

@media print {
    header, footer,
    .sf-sidebar, .sf-sidebar-scrim,
    .sf-toasts, .sf-skip-link,
    .sf-no-print { display: none !important; }
    .sf-main { margin-left: 0 !important; }
    .lg\:pl-64 { padding-left: 0 !important; }
    body { background: #fff; }
}

/* =========================================================================
   HOMEPAGE COMPOSITION, SECOND PASS
   =========================================================================
   New hooks for the markup being written above the fold: a hero that is a
   shell with the copy one side and a drawing the other, a browser window and a
   phone drawn from scratch, three cards floated over them, a flow strip, the
   theme showcase grid, a device switcher with a stage whose width it sets, the
   two-language guide, a true 16:9 video box, and one rule so Bangla renders in
   a face the reader already has.

   Nothing above this line is touched. These are new names sitting beside the
   older .sf-mock-* / .sf-hero-visual family rather than a rewrite of it, so
   both compositions keep rendering while the markup crosses over.

   Tokens are the ones the rest of this file uses and nothing new: var(--sf-brand),
   --sf-brand-dark, --sf-accent, --sf-ink, --sf-surface, --sf-subtle, --sf-line.
   Breakpoints are the two this file already has, 640px and 1024px, and every
   base rule is the small-screen one. No stylesheet import, no webfont, no URL of
   any kind: this section works with the network unplugged.
   ========================================================================= */

/* The hero shell. One column on a phone, two from 1024px. overflow-x:clip is
   the promise that nothing in the drawing can hand the page a horizontal
   scrollbar — a floated card is meant to sit a few pixels outside its column —
   and clip rather than hidden creates no scroll container, so the vertical axis
   is left alone. .sf-hero-copy already carries order:-1 further up this file,
   which is what puts the headline first on a phone whichever order the markup
   writes the two columns in. */
.sf-hero-shell {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(2rem, 6vw, 3rem);
    align-items: center;
    min-width: 0;
    overflow-x: clip;
}
/* min-width:0 lets a grid track shrink below its contents instead of widening
   the page — the one thing a long unbroken string in either column can do. */
.sf-hero-shell > .sf-hero-copy,
.sf-hero-shell > .sf-hero-art { min-width: 0; }

/* The drawing's column, and the positioning context for everything floated
   over it. isolation keeps the halo added at 1024px inside this element, so a
   z-index:-1 decoration cannot fall behind the page background. */
.sf-hero-art {
    position: relative;
    isolation: isolate;
    min-width: 0;
}

/* The browser window. One local custom property for the radius, because the
   frame and both panes inside it have to agree on it, and calc() on the panes so
   their corners stay right if one is ever drawn on its own. The shadow is two
   layers: a hairline that reads as the screen's edge, and a wide low-opacity
   cast that only reads as depth. Type is clamp()ed rather than scaled by a
   transform, so the window narrows while its labels keep a size worth reading. */
.sf-browser {
    --sf-browser-radius: clamp(.85rem, 2.6vw, 1.1rem);
    position: relative;
    overflow: hidden;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    border: 1px solid var(--sf-line);
    border-radius: var(--sf-browser-radius);
    background-color: var(--sf-surface);
    font-size: clamp(.8125rem, 2.6vw, .9375rem);
    box-shadow: 0 1px 2px rgba(15, 23, 42, .06),
                0 30px 60px -34px rgba(15, 23, 42, .45);
}

/* height:auto so a taller row of chrome than a fixed height utility allows for
   can never be clipped; min-height so the bar cannot collapse without one. */
.sf-browser-bar {
    display: flex;
    align-items: center;
    gap: .4rem;
    height: auto;
    min-height: clamp(2.05rem, 6.5vw, 2.25rem);
    padding-inline: clamp(.55rem, 2.4vw, .8rem);
    border-bottom: 1px solid var(--sf-line);
    border-radius: calc(var(--sf-browser-radius, 1rem) - 1px)
                   calc(var(--sf-browser-radius, 1rem) - 1px) 0 0;
    background-color: var(--sf-subtle);
    color: #64748b;
    font-size: .75rem;
}

/* The three traffic dots. Geometry is ours; the colour is only a default, and
   :where() is what makes it one — it contributes nothing to specificity, so a
   bg-* utility on the dot itself still wins and the markup stays in charge of
   the palette. Counted by adjacent siblings rather than :nth-child(), because
   the bar also carries a URL label and a dot must not be coloured by its
   position among that label's siblings. currentColor is the base fill, so an
   engine that cannot parse :where() still draws three grey dots rather than
   three invisible ones. */
.sf-browser-dot {
    flex: 0 0 auto;
    width: clamp(.5rem, 1.9vw, .6875rem);
    height: clamp(.5rem, 1.9vw, .6875rem);
    border-radius: 9999px;
    background-color: currentColor;
}
:where(.sf-browser-dot) { background-color: #fca5a5; }
:where(.sf-browser-dot + .sf-browser-dot) { background-color: #fcd34d; }
:where(.sf-browser-dot + .sf-browser-dot + .sf-browser-dot) { background-color: #6ee7b7; }

/* The pane the storefront is drawn in. Fluid padding is most of what keeps a
   three-across row of tiles inside a 320px column: at that width it gives back
   the nine pixels a fixed 1.25rem would have spent on air. */
.sf-browser-body {
    min-width: 0;
    padding: clamp(.65rem, 3.2vw, 1.25rem);
    border-radius: 0 0 calc(var(--sf-browser-radius, 1rem) - 1px)
                         calc(var(--sf-browser-radius, 1rem) - 1px);
    background-color: var(--sf-surface);
}

/* The phone: a bezel and nothing else, so whatever the markup puts inside it is
   what shows. No position, no inset, no z-index and no width stated here — where
   the phone sits in the drawing is the composition's business, and two files
   fighting over one property is how a composition breaks. max-width is the one
   exception, and it is a promise rather than a placement: a phone floated on a
   320px screen cannot widen the page. */
.sf-phone {
    overflow: hidden;
    max-width: 100%;
    padding: clamp(.25rem, 1vw, .35rem);
    border-radius: clamp(1.1rem, 4.5vw, 1.6rem);
    background-color: var(--sf-ink);
    box-shadow: 0 20px 40px -20px rgba(15, 23, 42, .6);
}

/* The speaker pill at the top of the bezel. Drawn in white-alpha rather than a
   token because it sits on the bezel's ink, not on a surface. */
.sf-phone-notch {
    display: block;
    width: clamp(2.25rem, 9vw, 3.25rem);
    height: clamp(.2rem, .8vw, .3rem);
    margin: .2rem auto .3rem;
    border-radius: 9999px;
    background-color: rgba(255, 255, 255, .35);
}

/* The screen. Its own radius, so the bezel is free not to clip. */
.sf-phone-body {
    overflow: hidden;
    min-width: 0;
    border-radius: clamp(.85rem, 3.4vw, 1.25rem);
    background-color: var(--sf-surface);
}

/* The cards floated over the drawing. display:none is the base state on purpose:
   at 320px there is no gutter beside a full-width window for a card to float
   into, two of them land on each other, and a negative inset there pushes the
   whole page sideways. Two come back at 640px and the third at 1024px, placed by
   their modifiers and drifting from that width. Decoration throughout — nothing
   in them says anything the copy has not said already.

   Placement is this file's, by arrangement, exactly as .sf-showcase-card's
   transform is: the modifiers exist to place these three and nothing else, so
   the markup should not also position them. z-index is stated here, unlike the
   older .sf-mock-card, for the opposite reason — these are placed by the
   stylesheet, so their layer is the stylesheet's too, and 2 puts them over the
   window whichever order the markup writes them in. .sf-hero-art's isolation
   keeps that 2 local to the drawing. */
.sf-float-card {
    display: none;
    min-width: 0;
    max-width: 100%;
    padding: clamp(.6rem, 1.7vw, .85rem);
    border: 1px solid var(--sf-line);
    border-radius: .85rem;
    background-color: var(--sf-surface);
    font-size: clamp(.7rem, 1.5vw, .8125rem);
    line-height: 1.35;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .06),
                0 22px 45px -26px rgba(15, 23, 42, .45);
}

/* The flow strip: the moves of the build, one under the next on a phone with the
   arrows turned to point down, then a row from 640px. rotate rather than
   transform on the arrow, so it composes with a transform the markup may already
   be applying instead of replacing it — the same reasoning as the translate in
   the sf-float keyframes further up. */
.sf-flow {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: clamp(.6rem, 2.5vw, 1rem);
    min-width: 0;
}
.sf-flow-step {
    position: relative;
    display: flex;
    align-items: center;
    gap: .65rem;
    min-width: 0;
    padding: clamp(.7rem, 2.6vw, 1rem);
    border: 1px solid var(--sf-line);
    border-radius: .9rem;
    background-color: var(--sf-surface);
    transition: border-color .25s ease, box-shadow .25s ease;
}
/* :focus-within as well as :hover, so tabbing to a link inside a step lights the
   step the same way pointing at it does. */
.sf-flow-step:hover,
.sf-flow-step:focus-within {
    border-color: color-mix(in srgb, var(--sf-brand) 35%, var(--sf-line));
    box-shadow: 0 14px 30px -22px rgba(15, 23, 42, .45);
}
/* The chevron between two steps of the flow. Everything except the display mode
   is stated here; display is set on the line below, and only when the markup has
   not asked for the arrow to be gone.

   Why the split: includes/header.php loads assets/css/tailwind.css first and this
   sheet second, so of two rules with equal specificity this one wins. A flat
   .sf-flow-arrow { display: flex } therefore outranks Tailwind's .hidden, and
   `class="sf-flow-arrow hidden sm:block"` — the phone layout in
   includes/partials/hero-visual.php, where the flow is two columns and an arrow
   would point at the card below instead of the one beside it — showed the arrow
   anyway. :not(.hidden) stands aside for that opt-out while keeping the same
   weight everywhere else. */
.sf-flow-arrow {
    flex: 0 0 auto;
    align-self: center;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    color: var(--sf-brand);
    rotate: 90deg;
}
.sf-flow-arrow:not(.hidden) { display: flex; }
.sf-flow-arrow svg { width: 100%; height: 100%; }

/* The theme showcase grid. One column, two from 640px, three from 1024px — the
   same shape includes/partials/theme-showcase.php builds out of utilities,
   stated here for the case where the grid is this hook's own job. */
.sf-showcase-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(1rem, 2.6vw, 1.5rem);
    min-width: 0;
}
/* A card in this grid is a column: picture on top, body below, and the body free
   to take the slack so every card in a row ends level. .sf-showcase-card's own
   rules further up — the overflow, the lift, the two focus cases — still do the
   rest, and none of them is restated here. */
.sf-showcase-grid > .sf-showcase-card {
    display: flex;
    flex-direction: column;
}
/* A card the showcase filter has ruled out. The [hidden] attribute alone cannot
   do this: tailwind.css states it once in its preflight near the top of the file,
   and .flex on the card plus the rule directly above both set display further
   down at the same specificity, so the attribute loses on source order and the
   card stays on screen. Naming the card and the attribute together settles it.
   Kept as a display rule rather than visibility, so a filtered-out card leaves
   the grid's flow instead of holding an empty cell open in it. */
.sf-showcase-card[hidden],
.sf-showcase-grid > .sf-showcase-card[hidden] { display: none; }

/* The card's picture. A theme's own render, an <img> or an <iframe> goes inside
   and fills it; the subtle fill is only what shows before that lands. A height
   utility on the same element wins over the ratio by being a definite height, so
   this is a default shape rather than an argument with the markup. */
.sf-showcase-art {
    position: relative;
    overflow: hidden;
    min-width: 0;
    aspect-ratio: 16 / 10;
    background-color: var(--sf-subtle);
}
.sf-showcase-art > img,
.sf-showcase-art > iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
    object-fit: cover;
}
/* A slow, small push in on hover: enough to say the card is a live thing, not
   enough to read as a zoom. The standalone scale property rather than transform,
   so the -4px lift .sf-showcase-card already applies is left alone. */
.sf-showcase-art > img { transition: scale .35s ease; }
.sf-showcase-card:hover .sf-showcase-art > img { scale: 1.04; }

.sf-showcase-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: .4rem;
    min-width: 0;
    padding: clamp(.9rem, 2.4vw, 1.25rem);
}

/* The device switcher. A segmented control: one tinted shell, and buttons inside
   it that only change their fill. .sf-device-tab is a complete control on its
   own and does not want .sf-btn on it as well — it states its own border, fill
   and typography, which would strip a button variant's. .is-active is the hook
   the markup toggles; the two aria attributes are honoured beside it because a
   switch of this kind has to say which width is on in a way a screen reader can
   read, and a control that already maintains aria-pressed should not also have to
   maintain a class. */
.sf-device-tabs {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .25rem;
    max-width: 100%;
    padding: .25rem;
    border: 1px solid var(--sf-line);
    border-radius: .85rem;
    background-color: var(--sf-subtle);
}
.sf-device-tab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    padding: .45rem .85rem;
    border: 0;
    border-radius: .65rem;
    background-color: transparent;
    color: #475569;
    font: inherit;
    font-size: .8125rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color .2s ease, color .2s ease, box-shadow .2s ease;
}
.sf-device-tab:hover {
    color: var(--sf-ink);
    background-color: rgba(15, 23, 42, .05);
}
.sf-device-tab.is-active,
.sf-device-tab[aria-pressed="true"],
.sf-device-tab[aria-selected="true"] {
    background-color: var(--sf-surface);
    color: var(--sf-brand-dark);
    box-shadow: 0 1px 2px rgba(15, 23, 42, .12);
}

/* The ring in the darker brand ink with an extra pixel of offset, the same
   treatment .sf-hero-copy's buttons take further up: a tab sits inside a tinted
   shell, where the brand-coloured ring the preflight gives every :focus-visible
   can sink into the background. Never none. */
.sf-device-tab:focus-visible {
    outline: 2px solid var(--sf-brand-dark);
    outline-offset: 3px;
}

/* The stage. Width, not scale: a transform would take the click targets and the
   type down with the picture, so the three device states are a max-width and a
   centring margin and nothing else. 390 and 820 are preview.php's own toolbar
   numbers, so a visitor who compares this against the real preview sees the same
   layouts rather than two switchers that disagree about what a tablet is.
   .is-desktop fills, and says so out loud rather than relying on the base rule,
   because it is the state JavaScript has to be able to switch back to. */
.sf-device-stage {
    position: relative;
    width: 100%;
    min-width: 0;
    margin-inline: auto;
    overflow: hidden;
    border-radius: 1rem;
    background-color: var(--sf-surface);
    box-shadow: 0 18px 45px -25px rgba(15, 23, 42, .45);
    transition: max-width .25s ease;
}
.sf-device-stage.is-desktop { max-width: 100%; }
.sf-device-stage.is-tablet  { max-width: 820px; }
.sf-device-stage.is-mobile  { max-width: 390px; }
/* Whatever is being shown fills the stage, so narrowing it reflows the layout
   inside instead of letterboxing it. */
.sf-device-stage > iframe,
.sf-device-stage > img {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

/* The English / বাংলা switch on how-it-works.php. Written for the hook landing
   either on the group or on each link, because both are reasonable markup and
   this file cannot know which arrived: :has(> a) dresses the shell and matches
   nothing at all when the hook is on a link, and the item rules name both
   shapes. An engine without :has() simply gets the pills without a shell drawn
   round them, which is the safe direction for a rule to fail in. */
.sf-guide-lang:has(> a),
.sf-guide-lang:has(> button) {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .25rem;
    max-width: 100%;
    padding: .25rem;
    border: 1px solid var(--sf-line);
    border-radius: .85rem;
    background-color: var(--sf-subtle);
}
.sf-guide-lang > a,
.sf-guide-lang > button,
a.sf-guide-lang,
button.sf-guide-lang {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: .45rem .9rem;
    border: 0;
    border-radius: .65rem;
    background-color: transparent;
    color: #475569;
    font: inherit;
    font-size: .875rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    transition: background-color .2s ease, color .2s ease, box-shadow .2s ease;
}

/* The language you are already reading. aria-current is what how-it-works.php
   writes on that link, so it is the state this reads; .is-active and
   aria-pressed are here for a switch built out of buttons, which has no page to
   point aria-current at. */
.sf-guide-lang > [aria-current],
.sf-guide-lang > [aria-pressed="true"],
.sf-guide-lang > .is-active,
a.sf-guide-lang[aria-current],
button.sf-guide-lang[aria-pressed="true"],
.sf-guide-lang.is-active {
    background-color: var(--sf-surface);
    color: var(--sf-brand-dark);
    box-shadow: 0 1px 2px rgba(15, 23, 42, .12);
}
.sf-guide-lang > a:focus-visible,
.sf-guide-lang > button:focus-visible,
a.sf-guide-lang:focus-visible,
button.sf-guide-lang:focus-visible {
    outline: 2px solid var(--sf-brand-dark);
    outline-offset: 3px;
}

/* A step in the guide: layout and rhythm, and deliberately nothing else. The
   marker, the rail and the ordinal are the markup's, because an <ol> already
   announces "item N of M" and a second number drawn here would be read out
   twice. The fluid gap replaces a fixed one at the width where a phone needs the
   pixels for the text instead. */
.sf-guide-step {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: clamp(.75rem, 3vw, 1.25rem);
    min-width: 0;
}
.sf-guide-step > * { min-width: 0; }
.sf-guide-step + .sf-guide-step { margin-top: clamp(.75rem, 2.4vw, 1.25rem); }

/* The 16:9 video box.

   aspect-ratio is stated on the hook itself rather than on a variant of it, and
   min-height:fit-content is what makes that safe. Where .sf-video-frame IS the
   box — a div with a play button in the middle of it — the ratio decides the
   height and the box is a true 16:9. Where it is the <figure> that
   includes/partials/video-frame.php already ships, wrapping a heading, a stage
   and a caption, that content is always taller than 16:9 of its own width, so
   min-height wins and the shipped figure keeps exactly the height it has today.
   One rule, no :has(), and nothing above this line changed.

   min-height is stated twice on purpose. Both keywords mean "at least as tall as
   the content" in the block axis; max-content is the better-defined of the two
   and wins where it is understood, and one of them is always understood wherever
   aspect-ratio is, because aspect-ratio is the newer feature by two years. An
   engine that honours the ratio can therefore never be an engine that drops the
   floor under it. */
.sf-video-frame {
    position: relative;
    aspect-ratio: 16 / 9;
    min-height: fit-content;
    min-height: max-content;
}
/* The box's own dressing, kept off that figure on purpose: a dark player surface
   behind its heading and its caption would be a visible change to a page that
   already works. Same gradient and same inset hairline as .sf-video-stage above,
   so the two read as one product. */
.sf-video-frame:not(figure) {
    display: grid;
    place-items: center;
    overflow: hidden;
    border-radius: 1rem;
    background-color: #0b1220;
    background-image: linear-gradient(165deg, #16233c 0%, #0d1526 48%, #060a13 100%);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .09),
                0 22px 50px -30px rgba(15, 23, 42, .55);
}
/* 16:9 for an engine that has no aspect-ratio at all. Drawn on a pseudo-element,
   and only for a frame that is not that figure: 56.25% of blank space above a
   heading is the one place this pattern does harm. @supports rather than a bare
   declaration, because the pseudo has to disappear where aspect-ratio does work
   — and display:block with it, since a padding box cannot do its job as a grid
   item. Anything old enough to miss aspect-ratio understands @supports. */
@supports not (aspect-ratio: 16 / 9) {
    .sf-video-frame:not(figure) { display: block; }
    .sf-video-frame:not(figure)::before {
        content: "";
        display: block;
        padding-top: 56.25%;
    }
}
/* .sf-video-play is a decorative span in the partial today. The day a real player
   makes it a button, its ring has to read on near-black: white, and offset clear
   of the glass edge. */
.sf-video-play:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 4px;
}

/* Bangla, in a face the reader already has.

   No @font-face and no download: the stack names what is actually installed on
   the machines this site is read on — Noto Sans Bengali wherever a Noto set is
   present, Nirmala UI on Windows 8 and later, Vrinda on older Windows, and
   SolaimanLipi and Kalpurush, the two faces most commonly installed by hand in
   Bangladesh — before falling back to whatever the system calls sans-serif. All
   of them render the conjuncts; the order is about which one renders them well.

   Bangla is a conjunct script carrying marks above and below the line, so it
   wants the extra leading, and tracking that flatters a Latin line only prises
   its ligatures apart — hence letter-spacing:normal, which also undoes a tracking
   utility inherited from a Latin wrapper. word-break stays normal because
   breaking inside a Bangla word breaks the word; overflow-wrap is what keeps a
   long Latin run — a URL, a plan name — from widening the page instead. Same
   reasoning as the two :lang(bn) rules further up this file, which are more
   specific and therefore still win wherever they apply. */
[lang="bn"],
.sf-bn {
    font-family: 'Noto Sans Bengali', 'Nirmala UI', 'Vrinda', 'SolaimanLipi', 'Kalpurush', sans-serif;
    line-height: 1.75;
    letter-spacing: normal;
    word-break: normal;
    overflow-wrap: break-word;
    font-variant-ligatures: normal;
}

@media (min-width: 640px) {
    /* Two designs abreast. */
    .sf-showcase-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    /* The flow becomes a row and its arrows turn to point along it. */
    .sf-flow { flex-direction: row; align-items: center; }
    .sf-flow-step { flex: 1 1 0; }
    .sf-flow-arrow { rotate: none; }

    /* The floats are placed rather than in flow from here. The horizontal insets
       are small and negative on purpose — a card that overlaps the window's edge
       reads as sitting in front of it — and .sf-hero-shell's overflow-x:clip is
       what makes a negative inset safe to state at all. The third card waits for
       1024px: at this width two are company and three are a pile. */
    .sf-float-card {
        position: absolute;
        z-index: 2;
        width: clamp(8.5rem, 22vw, 11rem);
    }
    .sf-float-card.is-1,
    .sf-float-card.is-2 { display: block; }
    .sf-float-card.is-1 { top: clamp(.5rem, 4vw, 2rem); left: -.75rem; }
    .sf-float-card.is-2 { right: -.75rem; bottom: clamp(.5rem, 4vw, 2rem); }
}

@media (min-width: 1024px) {
    /* Two columns, the copy on the slightly wider track: a headline wants the
       room, and the drawing beside it reads better narrow than the copy does. */
    .sf-hero-shell {
        grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
        gap: clamp(2.5rem, 5vw, 4.5rem);
    }

    /* A brand halo under the drawing. On a pseudo-element, so no partial has to
       carry a spare div, and behind everything thanks to .sf-hero-art's own
       isolation. An engine without color-mix() drops the tint and keeps the page. */
    .sf-hero-art::before {
        content: "";
        position: absolute;
        inset: 8% -4% -10% -4%;
        z-index: -1;
        background: radial-gradient(closest-side,
            color-mix(in srgb, var(--sf-brand) 20%, transparent), transparent);
        pointer-events: none;
    }

    /* Depth, at the width where there is room to read it. Same two layers, the
       cast thrown further: still no sideways spread, so nothing is clipped. */
    .sf-browser {
        box-shadow: 0 1px 2px rgba(15, 23, 42, .06),
                    0 44px 80px -40px rgba(15, 23, 42, .5);
    }

    /* Eight pixels of drift, reusing the sf-float keyframes above, from the width
       where the drawing has room of its own to move in. A different duration and
       a negative delay per card, so the group never travels as one block and no
       card sits still waiting for its turn to start. */
    .sf-phone            { animation: sf-float 7s ease-in-out infinite; }
    .sf-float-card.is-1  { animation: sf-float 8s ease-in-out infinite; }
    .sf-float-card.is-2  { animation: sf-float 9s ease-in-out -1.5s infinite; }
    .sf-float-card.is-3 {
        display: block;
        bottom: 22%;
        left: clamp(-1rem, -1.2vw, -.25rem);
        animation: sf-float 10s ease-in-out -3s infinite;
    }

    /* Three designs across. */
    .sf-showcase-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Every move this section adds, in one place a reader can switch off — and it is
   one block, not one per component, so nothing new can be added upstairs and
   quietly miss it. The global block in tailwind.css already cuts every duration
   to a thousandth of a millisecond; this takes the drift, the push-in and the
   width tween away outright, and names the hover state because a class with
   :hover on it outranks the class on its own. The focus rings are untouched: they
   are not decoration. */
@media (prefers-reduced-motion: reduce) {
    .sf-phone,
    .sf-float-card,
    .sf-float-card.is-1,
    .sf-float-card.is-2,
    .sf-float-card.is-3 {
        animation: none;
        translate: none;
    }
    .sf-flow-step,
    .sf-device-tab,
    .sf-device-stage,
    .sf-guide-lang > a,
    .sf-guide-lang > button,
    a.sf-guide-lang,
    button.sf-guide-lang,
    .sf-showcase-art > img { transition: none; }
    .sf-showcase-card:hover .sf-showcase-art > img { scale: none; }
}

/* On paper the drawing is a picture of a website inside a page about building
   websites: keep the window, drop the ink and the decoration around it. Decided
   here rather than by whether a min-width query happens to match the paper, which
   is the reasoning the main print block further up sets out. */
@media print {
    .sf-hero-art::before { display: none; }
    .sf-phone,
    .sf-float-card { display: none; }
    .sf-browser,
    .sf-flow-step,
    .sf-device-stage { box-shadow: none; }
    .sf-video-frame:not(figure) {
        background: #f1f5f9;
        box-shadow: inset 0 0 0 1px #cbd5e1;
    }
}
