/* Playable puzzle boards and in-app screenshots for the puzzle rules pages and
   the home page's playground. Split out of product.css so a page that shows a
   board does not have to load every product layout with it. Colours come from
   the page's product accent (puzzle-themes.css on rules pages) or app world. */

/* ---------------------------------------------------------------------------
   Playable puzzle boards on the rules pages. The static SVG diagram stays in
   the container as the no-JS fallback and is replaced once a board loads.
   --------------------------------------------------------------------------- */

/* Flex, not grid: a percentage-width board with aspect-ratio inside an
   auto-sized grid track is a circular dependency, and the track collapses to the
   board's intrinsic height, letting the status text land on top of it. */
.puzzle-play__frame {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.puzzle-play__grid { grid-area: grid; }
.puzzle-play__clues--top { grid-area: top; }
.puzzle-play__clues--bottom { grid-area: bottom; }
.puzzle-play__clues--left { grid-area: left; }
.puzzle-play__clues--right { grid-area: right; }

.puzzle-play__clues {
    display: grid;
    gap: 3px;
    color: var(--product-accent);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: .82rem;
    font-variant-numeric: tabular-nums;
}

.puzzle-play__clues--top,
.puzzle-play__clues--bottom { grid-template-columns: repeat(var(--size, 4), 1fr); }
.puzzle-play__clues--left,
.puzzle-play__clues--right { grid-template-rows: repeat(var(--size, 4), 1fr); }
.puzzle-play__clues span { display: grid; place-items: center; min-width: 1.4rem; min-height: 1.4rem; }

.puzzle-play__grid {
    display: grid;
    grid-template-columns: repeat(var(--size, 6), 1fr);
    grid-template-rows: repeat(var(--size, 6), 1fr);
    gap: 2px;
    width: min(100%, 17rem);
    aspect-ratio: 1;
}

/* Clues live in the same grid as the board, so a size+2 track layout keeps every
   clue locked to its row or column no matter the board size. */
.puzzle-play__grid--clued {
    grid-template-columns: 1.6rem repeat(var(--size, 4), 1fr) 1.6rem;
    grid-template-rows: 1.6rem repeat(var(--size, 4), 1fr) 1.6rem;
    width: min(100%, 20rem);
}

.puzzle-play__clue {
    display: grid;
    place-items: center;
    color: var(--product-accent-strong);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: .8rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}



.puzzle-play__cell {
    display: grid;
    place-items: center;
    min-width: 0;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 3px;
    background: var(--bg-elevated);
    color: var(--text);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: clamp(.8rem, 2.2vw, 1.05rem);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: background 140ms ease, box-shadow 140ms ease;
}

.puzzle-play__cell.is-given {
    color: var(--product-accent-strong);
    background: var(--bg-muted);
    border-color: var(--line-strong);
    cursor: default;
}

.puzzle-play__cell.has-colour { color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, .45); }

/* Tents: a tree is a given, a tent is the answer, grass is a reminder. */
.puzzle-play__cell[data-value="3"] { color: #3f7a37; font-size: 1.15em; }
.puzzle-play__cell[data-value="1"] { color: #b5482f; }
.puzzle-play__cell[data-value="2"] { color: var(--text-soft); }

@media (hover: hover) and (pointer: fine) {
    .puzzle-play__cell:not(:disabled):hover { background: var(--product-accent-soft); }
}

.puzzle-play__cell:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.puzzle-play__cell.is-bad { border-color: #c4453f; box-shadow: inset 0 0 0 1px #c4453f; }

/* The box rules on a colour sudoku, drawn as heavier gaps. */


.puzzle-play__status {
    display: grid;
    margin: .85rem 0 0;
    color: var(--text-muted);
    font-size: .86rem;
    line-height: 1.5;
}

/* Both spans share one grid cell, so the box is always as tall as the longest
   message and the board never moves while you play. */
.puzzle-play__status-ghost,
.puzzle-play__status-live { grid-area: 1 / 1; }
.puzzle-play__status-ghost { visibility: hidden; }

.puzzle-play__status--bad { color: #a5302a; font-weight: 650; }
.puzzle-play__status--ok { color: var(--product-accent-strong); font-weight: 650; }

.puzzle-play__foot { margin-top: .5rem; }

.puzzle-play__reset {
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    padding: .45rem 1rem;
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    background: var(--bg-elevated);
    color: var(--text-muted);
    font-family: var(--font-sans);
    font-size: .8rem;
    font-weight: 700;
    cursor: pointer;
    transition: color 180ms ease, border-color 180ms ease;
}

.puzzle-play__reset:hover { color: var(--text); border-color: var(--product-accent-border); }
.puzzle-play__reset:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* Slant: the numbers belong to the lattice points between cells, not to cells and not
   to the edges, so they ride in their own layer over the grid, offset by half a cell.
   Out of the grid's flow, or they would become grid items and push the cells around.

   The cell grid loses its 3px gap here. With a gap, "half a cell" is no longer a clean
   fraction of the grid and the dots drift away from the corners; a continuous lattice
   with cell borders is both easier to align against and closer to how Slant is drawn. */
.puzzle-play__frame--slant .puzzle-play__grid {
    position: relative;
    gap: 0;
}

.puzzle-play__frame--slant .puzzle-play__grid::after {
    /* Each cell draws only its right and bottom hairline, so the lattice needs an
       outer frame or its top and left edges are simply missing. */
    content: "";
    position: absolute;
    inset: 0;
    border: 1px solid var(--line);
    pointer-events: none;
}

.puzzle-play__frame--slant .puzzle-play__cell {
    border-radius: 0;
    /* One shared hairline rather than a border per cell, so the lattice stays even. */
    box-shadow: inset -1px -1px 0 var(--line);
    position: relative;
    /* The glyph stays for screen readers and for copied text; the visible diagonal is
       drawn below, because a text glyph at this size is thin, differently shaped in
       every font, and never quite reaches the corners. */
    color: transparent;
}

.puzzle-play__frame--slant .puzzle-play__cell::before {
    content: "";
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity 140ms ease;
}

/* A hairline corner to corner. The gradient stops are tight so the edge stays crisp
   instead of blurring at large cell sizes. */
.puzzle-play__frame--slant .puzzle-play__cell[data-value="1"]::before {
    opacity: 1;
    background: linear-gradient(to bottom right, transparent calc(50% - 1.4px),
        var(--product-accent, var(--accent)) calc(50% - 1.4px),
        var(--product-accent, var(--accent)) calc(50% + 1.4px), transparent calc(50% + 1.4px));
}

.puzzle-play__frame--slant .puzzle-play__cell[data-value="2"]::before {
    opacity: 1;
    background: linear-gradient(to bottom left, transparent calc(50% - 1.4px),
        var(--product-accent, var(--accent)) calc(50% - 1.4px),
        var(--product-accent, var(--accent)) calc(50% + 1.4px), transparent calc(50% + 1.4px));
}

/* The wrong cell keeps its diagonal but recolours it, so you can see what you did. */
.puzzle-play__frame--slant .puzzle-play__cell.is-bad::before {
    background-image: linear-gradient(to bottom right, transparent calc(50% - 1.4px),
        var(--focus) calc(50% - 1.4px), var(--focus) calc(50% + 1.4px), transparent calc(50% + 1.4px));
}

.puzzle-play__frame--slant .puzzle-play__cell.is-bad[data-value="2"]::before {
    background-image: linear-gradient(to bottom left, transparent calc(50% - 1.4px),
        var(--focus) calc(50% - 1.4px), var(--focus) calc(50% + 1.4px), transparent calc(50% + 1.4px));
}

.puzzle-play__dots {
    position: absolute;
    top: 0;
    left: 0;
    /* One cell wider and taller than the grid, so (size + 1) equal tracks land exactly
       on the cell boundaries. */
    width: calc(100% + 100% / var(--size));
    height: calc(100% + 100% / var(--size));
    display: grid;
    grid-template-columns: repeat(calc(var(--size) + 1), 1fr);
    grid-template-rows: repeat(calc(var(--size) + 1), 1fr);
    /* Half of one of those tracks up and left puts every dot on a corner. */
    transform: translate(calc(-50% / (var(--size) + 1)), calc(-50% / (var(--size) + 1)));
    pointer-events: none;
}

.puzzle-play__dot {
    display: grid;
    place-items: center;
    font-size: .7rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}

.puzzle-play__dot.has-clue {
    /* A filled chip, so a number reads as belonging to the dot and not to a cell. */
    width: 1.35em;
    height: 1.35em;
    place-self: center;
    border-radius: 999px;
    background: var(--product-accent, var(--accent));
    color: var(--text-inverse, #fff);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .22);
}



/* Akari. The light is the point of the game, so lit cells are tinted: without that you
   see where the bulbs are but not what they achieve, which is most of the puzzle. */
.puzzle-play__frame--lights .puzzle-play__cell.is-wall {
    background: var(--text);
    color: var(--text-inverse);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    box-shadow: none;
}

.puzzle-play__frame--lights .puzzle-play__cell.is-lit {
    background: color-mix(in srgb, var(--product-accent, var(--accent)) 16%, var(--bg-elevated));
}

.puzzle-play__frame--lights .puzzle-play__cell[data-value="1"] {
    /* The bulb itself sits brighter than the light it casts. */
    background: color-mix(in srgb, var(--product-accent, var(--accent)) 42%, var(--bg-elevated));
    color: var(--text);
    font-size: 1.25rem;
    text-shadow: 0 0 10px color-mix(in srgb, var(--product-accent, var(--accent)) 70%, transparent);
}

.puzzle-play__frame--lights .puzzle-play__cell[data-value="2"] {
    /* The ruled-out dot is a note to self, so it stays quiet. */
    color: var(--text-soft);
    font-size: 1.4rem;
}

@media (prefers-reduced-motion: no-preference) {
    .puzzle-play__frame--lights .puzzle-play__cell { transition: background 160ms ease; }
}

/* Slitherlink. The moves are on the edges between dots, so the layout is the classic
   lattice: a (2n+1) grid where even/even is a dot, even/odd a horizontal edge, odd/even
   a vertical edge and odd/odd a cell holding its number. Edge tracks are thin and cell
   tracks wide, which is what makes an edge look like an edge. */
.puzzle-loop {
    display: grid;
    grid-template-columns: repeat(var(--size), 12px 1fr) 12px;
    grid-template-rows: repeat(var(--size), 12px 1fr) 12px;
    place-items: center;
    width: 100%;
    max-width: 22rem;
    aspect-ratio: 1;
}

.puzzle-loop__dot {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--line-strong);
}

.puzzle-loop__cell {
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
    color: var(--text);
    font-size: .9rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* The whole track is the tap target even though the drawn line is a hairline, or an edge
   would be a 4px sliver on a phone. */
.puzzle-loop__edge {
    position: relative;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.puzzle-loop__edge::before {
    content: "";
    position: absolute;
    border-radius: 2px;
    background: transparent;
    transition: background 120ms ease, transform 120ms ease;
}

.puzzle-loop__edge--h::before { inset: 50% 12% auto; height: 3px; transform: translateY(-50%); }
.puzzle-loop__edge--v::before { inset: 12% 50% auto auto; width: 3px; height: 76%; transform: translateX(50%); }

.puzzle-loop__edge[data-value="1"]::before { background: var(--product-accent, var(--accent)); }
.puzzle-loop__edge.is-bad[data-value="1"]::before { background: var(--focus); }

/* Ruled out is a small cross, not a line: it is a note, and it must never be mistaken
   for part of the loop. */
.puzzle-loop__edge[data-value="2"]::after {
    content: "\00D7";
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    color: var(--text-soft);
    font-size: .7rem;
    line-height: 1;
}

.puzzle-loop__edge:hover::before { background: color-mix(in srgb, var(--product-accent, var(--accent)) 40%, transparent); }
.puzzle-loop__edge[data-value="1"]:hover::before { background: var(--product-accent, var(--accent)); }
.puzzle-loop__edge:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.puzzle-loop__cell.is-bad { color: var(--focus); }

@media (prefers-reduced-motion: reduce) {
    .puzzle-loop__edge::before { transition: none; }
}

/* Hashi. Islands sit on a square lattice and the tap target is the channel between two
   of them, so channels are grid items spanning from one island to the other and the
   islands are drawn on top of the bridge ends. */
.puzzle-bridges {
    position: relative;
    display: grid;
    grid-template-columns: repeat(var(--size), 1fr);
    grid-template-rows: repeat(var(--size), 1fr);
    place-items: center;
    width: 100%;
    max-width: 23rem;
    aspect-ratio: 1;
}

.puzzle-bridges__island {
    position: relative;
    z-index: 2;
    display: grid;
    place-items: center;
    width: 84%;
    aspect-ratio: 1;
    border: 2px solid var(--product-accent, var(--accent));
    border-radius: 50%;
    background: var(--bg-elevated);
    color: var(--text);
    font-size: .9rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.puzzle-bridges__island.is-bad {
    border-color: var(--focus);
    color: var(--focus);
}

.puzzle-bridges__link {
    position: relative;
    z-index: 1;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

/* One line for a single bridge, two for a double, drawn with a gradient so the pair
   stays evenly spaced whatever the channel length. */
.puzzle-bridges__link::before {
    content: "";
    position: absolute;
    inset: 0;
    background: transparent;
    transition: background 130ms ease;
}

.puzzle-bridges__link--h[data-value="1"]::before {
    background: linear-gradient(to bottom, transparent calc(50% - 1.5px),
        var(--product-accent, var(--accent)) calc(50% - 1.5px),
        var(--product-accent, var(--accent)) calc(50% + 1.5px), transparent calc(50% + 1.5px));
}

.puzzle-bridges__link--h[data-value="2"]::before {
    background: linear-gradient(to bottom, transparent calc(50% - 5px),
        var(--product-accent, var(--accent)) calc(50% - 5px),
        var(--product-accent, var(--accent)) calc(50% - 2px), transparent calc(50% - 2px),
        transparent calc(50% + 2px), var(--product-accent, var(--accent)) calc(50% + 2px),
        var(--product-accent, var(--accent)) calc(50% + 5px), transparent calc(50% + 5px));
}

.puzzle-bridges__link--v[data-value="1"]::before {
    background: linear-gradient(to right, transparent calc(50% - 1.5px),
        var(--product-accent, var(--accent)) calc(50% - 1.5px),
        var(--product-accent, var(--accent)) calc(50% + 1.5px), transparent calc(50% + 1.5px));
}

.puzzle-bridges__link--v[data-value="2"]::before {
    background: linear-gradient(to right, transparent calc(50% - 5px),
        var(--product-accent, var(--accent)) calc(50% - 5px),
        var(--product-accent, var(--accent)) calc(50% - 2px), transparent calc(50% - 2px),
        transparent calc(50% + 2px), var(--product-accent, var(--accent)) calc(50% + 2px),
        var(--product-accent, var(--accent)) calc(50% + 5px), transparent calc(50% + 5px));
}

.puzzle-bridges__link.is-bad::before { filter: hue-rotate(150deg) saturate(2); }
.puzzle-bridges__link:hover::before { background-color: color-mix(in srgb, var(--product-accent, var(--accent)) 8%, transparent); }
.puzzle-bridges__link:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

@media (prefers-reduced-motion: reduce) {
    .puzzle-bridges__link::before { transition: none; }
}

/* Real screenshots of the game apps on the puzzle guide pages.
   Separate from .phone-carousel above, which is sized for the framed Cosmic
   Orange iPhone asset at 1335x2734. These are raw device captures at two
   different aspect ratios - iPhone 1206x2622 and Android 1080x2400 - so the
   stage holds one ratio and the image is fitted inside it rather than stretched
   to it. The bezel is a plain rounded clip: pretending to be a specific handset
   would be a lie about the Android shots, which came off a different device.
   Colour comes from the page's own game accent, so each of the nine looks like
   its own product rather than like a shared template. */
.game-shots__carousel {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(.4rem, 2vw, 1.1rem);
}

.game-shots__frame { position: relative; z-index: 1; width: min(100%, 300px); margin: 0; }

.game-shots__stage {
    position: relative;
    aspect-ratio: 1206 / 2622;
    border-radius: 1.6rem;
    background: color-mix(in srgb, var(--product-accent, var(--accent)) 8%, var(--surface-high, var(--bg-elevated)));
    box-shadow: 0 14px 30px rgba(20, 26, 23, .14),
        inset 0 0 0 1px color-mix(in srgb, var(--product-accent, var(--accent)) 22%, transparent);
    overflow: hidden;
}

.game-shot {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    opacity: 0;
    pointer-events: none;
    transition: opacity 260ms var(--ease-out, ease);
}

.game-shot.is-active { opacity: 1; pointer-events: auto; z-index: 2; }

.game-shot img {
    max-width: 100%;
    max-height: 100%;
    /* contain, not cover: a cropped screenshot would cut the board off. */
    object-fit: contain;
    display: block;
}

.game-shots__meta { display: grid; gap: .8rem; justify-items: center; margin-top: .95rem; }

.game-shots__captions {
    display: grid;
    justify-items: center;
    width: min(100%, 21rem);
    /* Two lines held open so the dots do not jump when the caption wraps. */
    min-height: calc(2 * 1.45em);
    margin: 0;
    text-align: center;
}

.game-shots__caption {
    grid-area: 1 / 1;
    margin: 0;
    color: var(--text-muted);
    font-size: .92rem;
    line-height: 1.45;
    opacity: 0;
    visibility: hidden;
    transition: opacity 200ms ease;
}

.game-shots__caption.is-active { opacity: 1; visibility: visible; }

.game-shots__dots { display: inline-flex; align-items: center; gap: .4rem; }

.game-shots__dot {
    width: .45rem;
    height: .45rem;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: var(--line-strong);
    cursor: pointer;
    transition: width 300ms var(--ease-out, ease), background 200ms ease;
}

.game-shots__dot.is-active { width: 1.35rem; background: var(--product-accent, var(--accent)); }
.game-shots__dot:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
    .game-shot { transition: none; }
    .game-shots__caption { transition: none; }
    .game-shots__dot { transition: none; }
}

