{"id":1996,"date":"2026-07-19T11:24:59","date_gmt":"2026-07-19T09:24:59","guid":{"rendered":"https:\/\/keypers.io\/?page_id=1996"},"modified":"2026-07-21T11:32:32","modified_gmt":"2026-07-21T09:32:32","slug":"serp-simulator","status":"publish","type":"page","link":"https:\/\/keypers.io\/cs\/serp-simulator\/","title":{"rendered":"SERP simul\u00e1tor"},"content":{"rendered":"\t\t\t\t<div class=\"component-gutenberg-html prose prose-lg max-w-full\">\n\t\t\t\t\t<div class=\"container\">\n\t\t\t\t\t\t\t<div class=\"max-w-prose mx-auto\">\n\t\t\n<!-- ==================================================================\n     Keypers SERP simul\u00e1tor \u2014 WordPress embed build.\n\n     Paste this WHOLE block into a \"Vlastn\u00ed HTML\" (Custom HTML) block on the\n     page \u2014 not a Paragraph\/Classic block (that would let wpautop inject\n     <br>\/<p> and break it). One instance per page. After publishing, purge\n     the Breeze cache.\n\n     It is the standalone serp-simulator.html rewritten to live safely inside\n     the keypers.io theme:\n       \u2022 everything is wrapped in <div class=\"kp-serp\"> and every CSS rule is\n         scoped to it; every class is prefixed kp- and every id kp-, so the\n         tool cannot restyle the site and the theme cannot bleed into the tool;\n       \u2022 the tool gives itself a bit more width than the article column so the\n         form and the live Google preview sit SIDE BY SIDE (the preview is the\n         hero \u2014 it shouldn't be a scroll away). It stays centred and capped, and\n         on narrow screens it stacks with the preview leading;\n       \u2022 the layout breakpoint is a CONTAINER query, so it responds to the tool's\n         own width, not the browser window.\n     Design tokens and the light-only decision are unchanged \u2014 see the comments.\n     ================================================================== -->\n<div class=\"kp-serp\">\n<style>\n  \/* ------------------------------------------------------------------\n     Design tokens, lifted verbatim from the live Keypers theme:\n     keypers.io\/wp-content\/themes\/keypers\/static\/dist\/css\/style.min.css\n     Kept in oklch so the values match the source system exactly.\n\n     Single light theme on purpose. This drops into a white WordPress page on\n     keypers.io \u2014 a viewer-triggered dark flip would leave the tool inverted\n     inside a light site, which reads as broken rather than considered. Tokens\n     live on .kp-serp (not :root) so they inherit to our elements without\n     leaking to the rest of the page.\n     ------------------------------------------------------------------ *\/\n  .kp-serp {\n    color-scheme: light;\n\n    --kp-primary-50:  oklch(91.67% .0409 281.03);  \/* \u2248 #DEE1FF, the soft tint *\/\n    --kp-primary-100: oklch(84.3% .085 291.99);\n    --kp-primary-200: oklch(68.95% .174 288.5);\n    --kp-primary-300: oklch(55.25% .255 281.77);\n    --kp-primary-400: oklch(46.94% .302 270.62);\n    --kp-primary-500: oklch(39.19% .258 269.44);   \/* the ink *and* the fill *\/\n    --kp-logo:        oklch(42.59% .274 272.12);\n\n    --kp-teal-700:   oklch(51.1% .096 186.391);\n    --kp-orange-700: oklch(55.3% .195 38.402);\n    --kp-red-700:    oklch(50.5% .213 27.518);\n\n    \/* Booton is the Keypers brand face, served from the theme on keypers.io,\n       so this inherits the real font once embedded there; opened as a\n       standalone file it falls back to the system stack by design. *\/\n    --font-sans: \"Booton\", system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace;\n\n    \/* Semantic surface tokens \u2014 components read only these.\n       keypers.io sets no black anywhere: body, headings and nav all compute\n       to primary-500, so indigo is the text colour, not just an accent. *\/\n    --bg: #fff;\n    --bg-panel: #fff;\n    --bg-sunken: oklch(97.8% .012 283);            \/* faint indigo wash, never grey *\/\n    --fg: var(--kp-primary-500);\n    \/* The brand ramp is built for display type: primary-400 is both lighter\n       *and* more saturated than 500, so it fights the reader as body text.\n       These two hold the brand hue but dial chroma back to something readable,\n       and both clear WCAG AA on white (measured 5.76:1 \/ 4.79:1). *\/\n    --fg-muted: oklch(52% .16 275);\n    --fg-faint: oklch(56% .12 278);\n    --line: var(--kp-primary-50);\n    --line-strong: var(--kp-primary-100);\n    --accent: var(--kp-primary-500);\n    --accent-fg: #fff;\n    --accent-soft: var(--kp-primary-50);\n    --ok: var(--kp-teal-700);\n    --warn: var(--kp-orange-700);\n    --over: var(--kp-red-700);\n    --bezel: var(--kp-primary-50);\n    --bezel-line: var(--kp-primary-100);\n    --shadow: 0 1px 2px oklch(39% .1 269 \/ .07), 0 10px 30px oklch(39% .1 269 \/ .10);\n\n    \/* Filled controls on keypers.io are full pills (radius 999999px). *\/\n    --pill: 999px;\n\n    \/* Base type, formerly on <body>. Now confined to the tool. *\/\n    color: var(--fg);\n    font-family: var(--font-sans);\n    font-size: 15px;\n    line-height: 1.55;\n    -webkit-font-smoothing: antialiased;\n\n    \/* Modest breakout: the article column on keypers.io is ~985px, which is\n       too narrow to put the form and the 666px preview side by side \u2014 so the\n       tool would always stack and the preview (the whole point) would sit\n       below the fold. Give the tool a touch more room, centred on the column\n       and capped so it never reaches the viewport edge or triggers a\n       horizontal scrollbar. `(100% - W)\/2` centres W on the parent column\n       (negative when W is wider), and `100vw - 24px` keeps a safe gutter. The\n       site already uses full-width sections (header, newsletter), so a wider\n       tool block reads as intentional, not as an escapee. *\/\n    --kp-w: min(1200px, calc(100vw - 24px));\n    width: var(--kp-w);\n    margin-inline: calc((100% - var(--kp-w)) \/ 2);\n    min-width: 320px;   \/* never collapse under a shrink-to-fit parent *\/\n\n    \/* Lay out against the tool's own width, so the stack breakpoint reacts to\n       the tool, not the browser window. *\/\n    container-type: inline-size;\n    container-name: kpserp;\n  }\n\n  .kp-serp *,\n  .kp-serp *::before,\n  .kp-serp *::after { box-sizing: border-box; }\n\n  \/* Visually-hidden but available to screen readers. *\/\n  .kp-serp .kp-sr-only {\n    position: absolute;\n    width: 1px; height: 1px;\n    padding: 0; margin: -1px;\n    overflow: hidden;\n    clip: rect(0, 0, 0, 0);\n    white-space: nowrap;\n    border: 0;\n  }\n\n  .kp-serp .kp-wrap {\n    max-width: 1240px;\n    margin: 0 auto;\n    padding: 8px 16px 16px;   \/* side padding so full-bleed hosts don't hug edges *\/\n  }\n\n  \/* ---------------- header ---------------- *\/\n  .kp-serp .kp-masthead {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: flex-end;\n    justify-content: space-between;\n    gap: 20px;\n    padding-bottom: 20px;\n    border-bottom: 1px solid var(--line);\n    margin-bottom: 32px;\n  }\n  .kp-serp h1 {\n    font-size: 20px;\n    font-weight: 600;\n    margin: 0;\n    color: var(--fg);\n    letter-spacing: -.01em;\n    text-wrap: balance;\n  }\n  .kp-serp .kp-tagline {\n    margin: 6px 0 0;\n    color: var(--fg-muted);\n    font-size: 14px;\n    max-width: 58ch;\n  }\n\n  \/* ---------------- device switch ---------------- *\/\n  .kp-serp .kp-switch {\n    display: inline-flex;\n    background: var(--bg-sunken);\n    border: 1px solid var(--line);\n    border-radius: var(--pill);\n    padding: 3px;\n    gap: 2px;\n  }\n  .kp-serp .kp-switch button {\n    appearance: none;\n    border: 0;\n    background: transparent;\n    color: var(--fg-muted);\n    font-family: inherit;\n    font-size: 13px;\n    font-weight: 500;\n    padding: 7px 18px;\n    border-radius: var(--pill);\n    cursor: pointer;\n    transition: background .15s, color .15s;\n  }\n  .kp-serp .kp-switch button:hover { color: var(--fg); }\n  .kp-serp .kp-switch button[aria-pressed=\"true\"] {\n    background: var(--accent);\n    color: var(--accent-fg);\n  }\n  .kp-serp .kp-switch button:focus-visible {\n    outline: 2px solid var(--accent);\n    outline-offset: 2px;\n  }\n\n  \/* ---------------- layout ---------------- *\/\n  .kp-serp .kp-cols {\n    display: grid;\n    grid-template-columns: minmax(0, 1fr) 666px;\n    gap: 48px;\n    align-items: start;\n  }\n  \/* Stack once the tool is narrower than ~1100px rather than letting the\n     preview shrink \u2014 a squeezed preview is a wrong preview. Container query:\n     watches .kp-serp, so it reflects the tool's own (possibly broken-out)\n     width. When stacked, the preview leads (order:-1) so the hero is still the\n     first thing you see; DOM\/tab order stays inputs-first for keyboard users. *\/\n  @container kpserp (max-width: 1100px) {\n    .kp-serp .kp-cols { grid-template-columns: minmax(0, 1fr); gap: 40px; }\n    .kp-serp .kp-preview-col { position: static !important; order: -1; }\n    \/* the skeletons are only there to fill the desktop two-column space;\n       stacked, they'd just bury the inputs, so drop them. *\/\n    .kp-serp .kp-skeletons { display: none; }\n  }\n  \/* Fallback for engines without container-query support: use the viewport. *\/\n  @supports not (container-type: inline-size) {\n    @media (max-width: 1100px) {\n      .kp-serp .kp-cols { grid-template-columns: minmax(0, 1fr); gap: 40px; }\n      .kp-serp .kp-preview-col { position: static !important; order: -1; }\n      .kp-serp .kp-skeletons { display: none; }\n    }\n  }\n\n  .kp-serp .kp-fields { display: flex; flex-direction: column; gap: 28px; }\n\n  .kp-serp .kp-field { display: flex; flex-direction: column; gap: 8px; }\n\n  .kp-serp .kp-field-head {\n    display: flex;\n    align-items: baseline;\n    justify-content: space-between;\n    gap: 12px;\n  }\n  .kp-serp label {\n    font-size: 13px;\n    font-weight: 600;\n    color: var(--fg);\n    letter-spacing: .02em;\n  }\n  .kp-serp .kp-label-hint {\n    font-weight: 400;\n    color: var(--fg-faint);\n    font-size: 12px;\n  }\n\n  \/* Scoped to .kp-field so this styles ONLY the three real form inputs, never\n     the decorative Google search field. box-shadow:none keeps a theme's own\n     input shadow from bleeding in. *\/\n  .kp-serp .kp-field input[type=\"text\"],\n  .kp-serp .kp-field textarea {\n    width: 100%;\n    font-family: inherit;\n    font-size: 14px;\n    line-height: 1.5;\n    color: var(--fg);\n    background: var(--bg-panel);\n    border: 1px solid var(--line-strong);\n    border-radius: 10px;\n    box-shadow: none;\n    padding: 11px 14px;\n    transition: border-color .15s, box-shadow .15s;\n  }\n  \/* 2px outline on focus (mirrors the switch) \u2014 a reliable, forced-colors-safe\n     indicator, not just a 1px border tint. *\/\n  .kp-serp .kp-field input[type=\"text\"]:focus,\n  .kp-serp .kp-field textarea:focus {\n    outline: 2px solid var(--accent);\n    outline-offset: 2px;\n    border-color: var(--accent);\n  }\n  .kp-serp .kp-field textarea { resize: vertical; min-height: 88px; }\n\n  \/* ---------------- the pixel ruler ----------------\n     The point of the whole tool: characters lie, pixels don't.\n     The track IS the limit. The tick is where Google cuts. *\/\n  .kp-serp .kp-meter { display: flex; flex-direction: column; gap: 6px; }\n\n  .kp-serp .kp-track {\n    position: relative;\n    height: 6px;\n    background: var(--bg-sunken);\n    border: 1px solid var(--line);\n    border-radius: 999px;\n    overflow: hidden;\n  }\n  .kp-serp .kp-fill {\n    position: absolute;\n    inset: 0 auto 0 0;\n    background: var(--ok);\n    border-radius: 999px;\n    transition: width .12s linear, background .2s;\n  }\n  .kp-serp .kp-fill[data-state=\"warn\"] { background: var(--warn); }\n  .kp-serp .kp-fill[data-state=\"over\"] { background: var(--over); }\n  .kp-serp .kp-tick {\n    position: absolute;\n    top: -3px;\n    bottom: -3px;\n    width: 1px;\n    background: var(--fg-faint);\n    opacity: .5;\n  }\n\n  .kp-serp .kp-readout {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    gap: 12px;\n    font-size: 12px;\n    color: var(--fg-muted);\n  }\n  .kp-serp .kp-nums {\n    font-family: var(--font-mono);\n    font-variant-numeric: tabular-nums;\n    font-size: 11.5px;\n    letter-spacing: -.01em;\n  }\n  .kp-serp .kp-nums b { color: var(--fg); font-weight: 600; }\n  .kp-serp .kp-nums .kp-sep { opacity: .45; }\n\n  \/* state pill \u2014 state readable as form, not just colour *\/\n  .kp-serp .kp-pill {\n    display: inline-flex;\n    align-items: center;\n    gap: 5px;\n    font-size: 11px;\n    font-weight: 600;\n    letter-spacing: .03em;\n    text-transform: uppercase;\n    padding: 2px 8px;\n    border-radius: 999px;\n    border: 1px solid currentColor;\n    white-space: nowrap;\n  }\n  .kp-serp .kp-pill::before {\n    content: \"\";\n    width: 5px; height: 5px;\n    border-radius: 50%;\n    background: currentColor;\n  }\n  .kp-serp .kp-pill[data-state=\"ok\"]   { color: var(--ok); }\n  .kp-serp .kp-pill[data-state=\"warn\"] { color: var(--warn); }\n  .kp-serp .kp-pill[data-state=\"over\"] { color: var(--over); }\n\n  \/* ---------------- preview ---------------- *\/\n  .kp-serp .kp-preview-col { position: sticky; top: 32px; }\n\n  .kp-serp .kp-preview-head {\n    display: flex;\n    align-items: baseline;\n    justify-content: space-between;\n    gap: 12px;\n    margin-bottom: 10px;\n  }\n  .kp-serp .kp-eyebrow {\n    font-size: 11px;\n    font-weight: 600;\n    letter-spacing: .12em;\n    text-transform: uppercase;\n    color: var(--fg-faint);\n  }\n\n  \/* A screen inside a bezel: the preview is Google's surface, not ours, so it\n     keeps its own light rendering. Frame hugs the screen so the mobile view\n     reads as a phone, not a desktop with empty space beside it. *\/\n  .kp-serp .kp-bezel {\n    background: var(--bezel);\n    border: 1px solid var(--bezel-line);\n    border-radius: 14px;\n    padding: 14px;\n    box-shadow: var(--shadow);\n    width: fit-content;\n    max-width: 100%;\n  }\n  .kp-serp .kp-screen {\n    background: #fff;\n    border-radius: 6px;\n    padding: 20px 18px;\n    overflow-x: auto;\n  }\n  .kp-serp .kp-screen[data-device=\"mobile\"] { padding: 16px 14px; }\n\n  \/* Everything below renders in Google's own type, never Booton. *\/\n  .kp-serp .kp-snippet { font-family: Arial, \"Liberation Sans\", sans-serif; }\n  \/* Fixed width on purpose: the SERP column is 600px, so the preview must be\n     600px too. Letting it shrink to the layout would show line breaks Google\n     never makes and quietly contradict the pixel readout. The page (search bar\n     + snippet + skeletons) holds that fixed width; narrow screens scroll the\n     screen instead. *\/\n  .kp-serp .kp-page { width: 600px; }\n  .kp-serp .kp-screen[data-device=\"mobile\"] .kp-page { width: 400px; }\n  .kp-serp .kp-snippet-inner { width: 100%; }\n\n  .kp-serp .kp-snippet-site {\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    margin-bottom: 4px;\n  }\n  .kp-serp .kp-snippet-favicon {\n    width: 26px; height: 26px;\n    border-radius: 50%;\n    background: #f1f3f4;\n    border: 1px solid #ecedef;\n    display: grid;\n    place-items: center;\n    font-size: 12px;\n    font-weight: 700;\n    color: #5f6368;\n    flex: none;\n  }\n  .kp-serp .kp-snippet-site-meta { min-width: 0; line-height: 1.2; }\n  .kp-serp .kp-snippet-site-name {\n    font-size: 14px;\n    color: #202124;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n  .kp-serp .kp-snippet-url {\n    font-size: 12px;\n    color: #4d5156;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n  .kp-serp .kp-snippet-title {\n    font-size: 20px;\n    line-height: 1.3;\n    color: #1a0dab;\n    margin: 2px 0 3px;\n    cursor: pointer;\n    word-break: break-word;\n  }\n  .kp-serp .kp-screen[data-device=\"mobile\"] .kp-snippet-title { font-size: 18px; }\n  .kp-serp .kp-snippet-title:hover { text-decoration: underline; }\n  .kp-serp .kp-snippet-desc {\n    font-size: 14px;\n    line-height: 1.58;\n    color: #4d5156;\n    word-break: break-word;\n  }\n\n  \/* Truncated text is shown, not deleted \u2014 you see what you're losing. *\/\n  .kp-serp .kp-ghost {\n    color: #bdc1c6;\n    background: repeating-linear-gradient(\n      -45deg,\n      transparent 0 3px,\n      oklch(90% .02 25 \/ .35) 3px 5px\n    );\n    border-radius: 2px;\n  }\n  .kp-serp .kp-snippet-empty { color: #5f6368; font-style: italic; }\n\n  .kp-serp .kp-cut-note {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    margin-top: 12px;\n    font-size: 12px;\n    color: var(--fg-muted);\n  }\n  .kp-serp .kp-cut-swatch {\n    width: 22px; height: 9px;\n    border-radius: 2px;\n    background: repeating-linear-gradient(\n      -45deg, #eceff1 0 3px, oklch(80% .06 25 \/ .5) 3px 5px\n    );\n    border: 1px solid var(--line);\n    flex: none;\n  }\n\n  \/* ---------------- Google chrome (decorative results-page mock) ----------------\n     Frames the live snippet as the top result on a mock Google page: a\n     non-functional search bar and tabs on top, greyed \"still loading\" skeleton\n     results below. Everything here is decoration; the skeletons hide when the\n     layout stacks so they don't bury the inputs on a phone. *\/\n  .kp-serp .kp-gbar {\n    display: flex;\n    align-items: center;\n    gap: 20px;\n    padding-bottom: 14px;\n  }\n  \/* Google's logo uses the proprietary \"Product Sans\", which can't be embedded;\n     this stack prefers geometric sans-serifs (a closer match where installed)\n     and falls back gracefully. For a pixel-exact logo, swap in Google's own\n     logo image. *\/\n  .kp-serp .kp-glogo {\n    font-family: \"Product Sans\", \"Google Sans\", Futura, \"Century Gothic\", \"Trebuchet MS\", system-ui, Arial, sans-serif;\n    font-size: 22px;\n    font-weight: 400;\n    letter-spacing: -.5px;\n    flex: none;\n    user-select: none;\n  }\n  .kp-serp .kp-glogo span:nth-child(1) { color: #4285F4; }\n  .kp-serp .kp-glogo span:nth-child(2) { color: #EA4335; }\n  .kp-serp .kp-glogo span:nth-child(3) { color: #FBBC05; }\n  .kp-serp .kp-glogo span:nth-child(4) { color: #4285F4; }\n  .kp-serp .kp-glogo span:nth-child(5) { color: #34A853; }\n  .kp-serp .kp-glogo span:nth-child(6) { color: #EA4335; }\n\n  .kp-serp .kp-gsearch {\n    display: flex;\n    align-items: center;\n    gap: 10px;\n    flex: 1;\n    min-width: 0;\n    border: 1px solid #dfe1e5;\n    border-radius: 24px;\n    padding: 8px 16px;\n    box-shadow: 0 1px 4px rgba(32, 33, 36, .08);\n  }\n  .kp-serp .kp-gmag { flex: none; }\n  \/* Fully reset so no theme input styling (border, radius, shadow, native\n     appearance) can draw a second \"box\" inside the search pill. *\/\n  .kp-serp .kp-ginput {\n    flex: 1;\n    min-width: 0;\n    border: 0;\n    border-radius: 0;\n    outline: 0;\n    box-shadow: none;\n    background: transparent;\n    -webkit-appearance: none;\n    appearance: none;\n    margin: 0;\n    padding: 0;\n    font-family: Arial, sans-serif;\n    font-size: 14px;\n    color: #202124;\n  }\n  .kp-serp .kp-ginput::placeholder { color: #9aa0a6; }\n\n  .kp-serp .kp-gtabs {\n    display: flex;\n    gap: 22px;\n    font-family: Arial, sans-serif;\n    font-size: 12px;\n    color: #5f6368;\n    border-bottom: 1px solid #ebebeb;\n    margin-bottom: 6px;\n  }\n  .kp-serp .kp-gtab { padding-bottom: 8px; }\n  .kp-serp .kp-gtab--on {\n    color: #1a73e8;\n    box-shadow: inset 0 -3px 0 #1a73e8;   \/* Google's blue active-tab underline *\/\n  }\n\n  \/* the live snippet, softly highlighted as the top result. The tint sits on a\n     pseudo-element so it can pad and round without changing the 600px text\n     width the preview depends on. *\/\n  .kp-serp .kp-snippet--yours { position: relative; margin: 2px 0 8px; }\n  .kp-serp .kp-snippet--yours::before {\n    content: \"\";\n    position: absolute;\n    inset: -10px -14px;\n    background: rgba(34, 0, 204, .05);\n    border-radius: 12px;\n    z-index: 0;\n  }\n  .kp-serp .kp-snippet--yours > * { position: relative; z-index: 1; }\n  .kp-serp .kp-yours-tag {\n    position: absolute;\n    top: 0;\n    right: 0;\n    font-family: var(--font-sans);\n    font-size: 10px;\n    font-weight: 600;\n    letter-spacing: .04em;\n    text-transform: uppercase;\n    color: var(--accent);\n    opacity: .8;\n  }\n\n  \/* skeleton \"still loading\" results *\/\n  .kp-serp .kp-skel { margin-top: 24px; }\n  .kp-serp .kp-skel-site {\n    display: flex; align-items: center; gap: 12px; margin-bottom: 10px;\n  }\n  .kp-serp .kp-skel-circle {\n    width: 26px; height: 26px; border-radius: 50%; background: #e5e7ea; flex: none;\n  }\n  .kp-serp .kp-skel-line { height: 12px; width: 150px; border-radius: 4px; background: #e5e7ea; }\n  .kp-serp .kp-skel-title {\n    height: 18px; width: 62%; border-radius: 4px;\n    background: rgba(34, 0, 204, .13); margin-bottom: 10px;\n  }\n  .kp-serp .kp-skel-desc { height: 40px; border-radius: 4px; background: #e5e7ea; }\n  \/* subtle \"loading\" pulse; the global prefers-reduced-motion rule disables it *\/\n  @keyframes kp-shimmer { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }\n  .kp-serp .kp-skel-circle,\n  .kp-serp .kp-skel-line,\n  .kp-serp .kp-skel-title,\n  .kp-serp .kp-skel-desc { animation: kp-shimmer 1.6s ease-in-out infinite; }\n  .kp-serp .kp-skel:nth-child(2) .kp-skel-title { animation-delay: .15s; }\n  .kp-serp .kp-skel:nth-child(3) .kp-skel-title { animation-delay: .3s; }\n  .kp-serp .kp-skel:nth-child(4) .kp-skel-title { animation-delay: .45s; }\n\n  \/* ---------------- caveats ---------------- *\/\n  .kp-serp .kp-caveats {\n    margin-top: 44px;\n    padding-top: 24px;\n    border-top: 1px solid var(--line);\n    display: grid;\n    \/* min(100%,260px) lets a track shrink below 260px on tiny screens instead\n       of forcing the whole page to scroll sideways. *\/\n    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));\n    gap: 24px 40px;\n  }\n  .kp-serp .kp-caveat h2 {\n    font-size: 12px;\n    font-weight: 600;\n    letter-spacing: .08em;\n    text-transform: uppercase;\n    color: var(--fg-faint);\n    margin: 0 0 6px;\n  }\n  .kp-serp .kp-caveat p {\n    margin: 0;\n    font-size: 13px;\n    line-height: 1.6;\n    color: var(--fg-muted);\n    max-width: 60ch;\n  }\n  \/* ---------------- z\u00e1vere\u010dn\u00e9 CTA ----------------\n     N\u00e1stroj predt\u00fdm kon\u010dil do pr\u00e1zdna a n\u00e1v\u0161tevn\u00edk odi\u0161iel. Toto je jedin\u00fd\n     odkaz z n\u00e1stroja sp\u00e4\u0165 na slu\u017ebu. Z\u00e1merne tich\u00e9: sed\u00ed a\u017e za caveatmi,\n     pomenuje hranicu n\u00e1stroja a pon\u00fakne to, \u010do za \u0148ou le\u017e\u00ed. Rovnak\u00e9 tokeny\n     ako zvy\u0161ok, \u017eiadna nov\u00e1 farba. *\/\n  .kp-serp .kp-cta {\n    margin-top: 40px;\n    padding: 26px 30px;\n    background: var(--bg-sunken);\n    border: 1px solid var(--line);\n    border-radius: 14px;\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    justify-content: space-between;\n    gap: 18px 32px;\n  }\n  .kp-serp .kp-cta-text { max-width: 62ch; }\n  .kp-serp .kp-cta h2 {\n    font-size: 15px;\n    font-weight: 600;\n    color: var(--fg);\n    margin: 0 0 4px;\n  }\n  .kp-serp .kp-cta p {\n    margin: 0;\n    font-size: 13px;\n    line-height: 1.6;\n    color: var(--fg-muted);\n  }\n  .kp-serp .kp-cta-btn {\n    flex: none;\n    display: inline-block;\n    background: var(--accent);\n    color: var(--accent-fg);\n    font-size: 14px;\n    font-weight: 500;\n    text-decoration: none;\n    padding: 11px 26px;\n    border-radius: var(--pill);\n    transition: opacity .15s;\n  }\n  .kp-serp .kp-cta-btn:hover { opacity: .88; color: var(--accent-fg); }\n  .kp-serp .kp-cta-btn:focus-visible {\n    outline: 2px solid var(--accent);\n    outline-offset: 2px;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .kp-serp *,\n    .kp-serp *::before,\n    .kp-serp *::after { transition: none !important; animation: none !important; }\n  }\n<\/style>\n\n<div class=\"kp-wrap\">\n  <header class=\"kp-masthead\">\n    <div>\n      <!-- No logo here on purpose: this page lives inside the keypers.io theme,\n           which already renders the site header. A second Keypers mark would\n           read as a widget dropped in rather than a native page. The tool keeps\n           only its functional chrome \u2014 a heading, one line of instructions, and\n           the device switch. This h1 is the page's heading; if the theme ever\n           starts printing its own visible page title, demote this to <h2>. -->\n      <h1>SERP simul\u00e1tor<\/h1>\n      <p class=\"kp-tagline\">\n        Napi\u0161te titulek a&nbsp;popisek a&nbsp;hned uvid\u00edte, jak se zobraz\u00ed ve v\u00fdsledc\u00edch\n        Googlu a&nbsp;kde je o\u0159\u00edzne.&nbsp;<\/p>\n    <\/div>\n    <div class=\"kp-switch\" role=\"group\" aria-label=\"Za\u0159\u00edzen\u00ed\">\n      <button type=\"button\" id=\"kp-btn-desktop\" aria-pressed=\"true\">Desktop<\/button>\n      <button type=\"button\" id=\"kp-btn-mobile\" aria-pressed=\"false\">Mobil<\/button>\n    <\/div>\n  <\/header>\n\n  <div class=\"kp-cols\">\n    <!-- ---------------- inputs ---------------- -->\n    <div class=\"kp-fields\">\n      <div class=\"kp-field\">\n        <div class=\"kp-field-head\">\n          <label for=\"kp-in-title\">Titulek str\u00e1nky<\/label>\n          <span class=\"kp-label-hint\" id=\"kp-hint-title\">&lt;title&gt;<\/span>\n        <\/div>\n        <!-- Caps are far above any real title\/description. They exist so a\n             stray paste of a whole document can't stall the tab: every\n             keystroke re-measures the full string on a canvas. -->\n        <input type=\"text\" id=\"kp-in-title\" spellcheck=\"false\" maxlength=\"500\" aria-describedby=\"kp-hint-title kp-nums-title kp-pill-title\" value=\"SEO audit pro e-shopy: najdeme, co v\u00e1s brzd\u00ed | Keypers\">\n        <div class=\"kp-meter\">\n          <div class=\"kp-track\" id=\"kp-track-title\">\n            <div class=\"kp-fill\" id=\"kp-fill-title\"><\/div>\n            <div class=\"kp-tick\" id=\"kp-tick-title\"><\/div>\n          <\/div>\n          <div class=\"kp-readout\">\n            <span class=\"kp-nums\" id=\"kp-nums-title\"><\/span>\n            <span class=\"kp-pill\" id=\"kp-pill-title\"><\/span>\n          <\/div>\n        <\/div>\n        <span class=\"kp-sr-only\" id=\"kp-status-title\" role=\"status\" aria-live=\"polite\"><\/span>\n      <\/div>\n\n      <div class=\"kp-field\">\n        <div class=\"kp-field-head\">\n          <label for=\"kp-in-desc\">Popisek<\/label>\n          <span class=\"kp-label-hint\" id=\"kp-hint-desc\">meta description<\/span>\n        <\/div>\n        <textarea id=\"kp-in-desc\" spellcheck=\"false\" maxlength=\"2000\" aria-describedby=\"kp-hint-desc kp-nums-desc kp-pill-desc\">Zjist\u00edme, pro\u010d v\u00e1\u0161 e-shop z organiky nevyd\u011bl\u00e1v\u00e1 tolik, kolik by mohl. Konkr\u00e9tn\u00ed zad\u00e1n\u00ed pro v\u00fdvoj\u00e1\u0159e, ne obecn\u00e1 doporu\u010den\u00ed. 11 let, 350 klient\u016f.<\/textarea>\n        <div class=\"kp-meter\">\n          <div class=\"kp-track\" id=\"kp-track-desc\">\n            <div class=\"kp-fill\" id=\"kp-fill-desc\"><\/div>\n            <div class=\"kp-tick\" id=\"kp-tick-desc\"><\/div>\n          <\/div>\n          <div class=\"kp-readout\">\n            <span class=\"kp-nums\" id=\"kp-nums-desc\"><\/span>\n            <span class=\"kp-pill\" id=\"kp-pill-desc\"><\/span>\n          <\/div>\n        <\/div>\n        <span class=\"kp-sr-only\" id=\"kp-status-desc\" role=\"status\" aria-live=\"polite\"><\/span>\n      <\/div>\n\n      <div class=\"kp-field\">\n        <div class=\"kp-field-head\">\n          <label for=\"kp-in-url\">URL adresa<\/label>\n          <span class=\"kp-label-hint\" id=\"kp-hint-url\">zobraz\u00ed se jako drobe\u010dky<\/span>\n        <\/div>\n        <input type=\"text\" id=\"kp-in-url\" spellcheck=\"false\" maxlength=\"500\" aria-describedby=\"kp-hint-url\" value=\"https:\/\/keypers.io\/cs\/seo-audit\/\">\n      <\/div>\n    <\/div>\n\n    <!-- ---------------- preview ---------------- -->\n    <div class=\"kp-preview-col\">\n      <div class=\"kp-preview-head\">\n        <span class=\"kp-eyebrow\">N\u00e1hled<\/span>\n        <span class=\"kp-eyebrow\" id=\"kp-device-label\">Desktop \u00b7&nbsp;Arial<\/span>\n      <\/div>\n\n      <div class=\"kp-bezel\">\n        <div class=\"kp-screen\" id=\"kp-screen\" data-device=\"desktop\">\n          <div class=\"kp-page\">\n            <!-- Decorative mock of a Google results page. The search field is a\n                 real input so a curious visitor can type in it, but nothing\n                 happens (it does not search). The logo and tabs are aria-hidden\n                 because they carry no information. -->\n            <div class=\"kp-gbar\">\n              <span class=\"kp-glogo\" aria-hidden=\"true\"><span>G<\/span><span>o<\/span><span>o<\/span><span>g<\/span><span>l<\/span><span>e<\/span><\/span>\n              <div class=\"kp-gsearch\">\n                <svg class=\"kp-gmag\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"7\" stroke=\"#9aa0a6\" stroke-width=\"2\" \/><line x1=\"16.5\" y1=\"16.5\" x2=\"21\" y2=\"21\" stroke=\"#9aa0a6\" stroke-width=\"2\" stroke-linecap=\"round\" \/><\/svg>\n                <input class=\"kp-ginput\" type=\"text\" tabindex=\"-1\" aria-label=\"Uk\u00e1zkov\u00e9 vyhled\u00e1vac\u00ed pole (nic nevyhled\u00e1)\" placeholder=\"Zadejte hledan\u00fd v\u00fdraz\">\n              <\/div>\n            <\/div>\n            <div class=\"kp-gtabs\" aria-hidden=\"true\">\n              <span class=\"kp-gtab kp-gtab--on\">V\u0161e<\/span>\n              <span class=\"kp-gtab\">Obr\u00e1zky<\/span>\n              <span class=\"kp-gtab\">Videa<\/span>\n              <span class=\"kp-gtab\">Zpr\u00e1vy<\/span>\n              <span class=\"kp-gtab\">Mapy<\/span>\n            <\/div>\n\n            <!-- The live snippet, marked as the highlighted top result. -->\n            <div class=\"kp-snippet kp-snippet--yours\">\n              <span class=\"kp-yours-tag\" aria-hidden=\"true\">V\u00e1\u0161 n\u00e1hled<\/span>\n              <div class=\"kp-snippet-inner\">\n                <div class=\"kp-snippet-site\">\n                  <div class=\"kp-snippet-favicon\" id=\"kp-favicon\">K<\/div>\n                  <div class=\"kp-snippet-site-meta\">\n                    <div class=\"kp-snippet-site-name\" id=\"kp-site-name\">Keypers<\/div>\n                    <div class=\"kp-snippet-url\" id=\"kp-site-url\"><\/div>\n                  <\/div>\n                <\/div>\n                <div class=\"kp-snippet-title\" id=\"kp-out-title\"><\/div>\n                <div class=\"kp-snippet-desc\" id=\"kp-out-desc\"><\/div>\n              <\/div>\n            <\/div>\n\n            <!-- \"Still loading\" skeleton results, purely to frame yours as the\n                 top result. Decorative, and hidden when the layout stacks so\n                 they don't bury the inputs on a phone. -->\n            <div class=\"kp-skeletons\" aria-hidden=\"true\">\n              <div class=\"kp-skel\"><div class=\"kp-skel-site\"><span class=\"kp-skel-circle\"><\/span><span class=\"kp-skel-line\"><\/span><\/div><div class=\"kp-skel-title\"><\/div><div class=\"kp-skel-desc\"><\/div><\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"kp-cut-note\">\n        <span class=\"kp-cut-swatch\"><\/span>\n        <span>\u0160rafovan\u00e1 \u010d\u00e1st se ve v\u00fdsledc\u00edch nezobraz\u00ed, proto\u017ee ji Google o\u0159\u00edzne.<\/span>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <!-- ---------------- caveats ----------------\n       These are the things that make it obvious SEO people built this,\n       not a developer who found measureText() on Stack Overflow. -->\n  <section class=\"kp-caveats\" aria-label=\"Pozn\u00e1mky k p\u0159esnosti\">\n    <div class=\"kp-caveat\">\n      <h2>Limity jsou odhad, ne&nbsp;dogma<\/h2>\n      <p>\n        Google \u017e\u00e1dn\u00e9 p\u0159esn\u00e9 hodnoty nepublikuje a&nbsp;zdroje se rozch\u00e1zej\u00ed.\n        U&nbsp;titulku m\u011b\u0159il Screaming Frog historicky 482&nbsp;px, dne\u0161n\u00ed n\u00e1stroje\n        pracuj\u00ed sp\u00ed\u0161 s&nbsp;580\u2009\u2013\u2009600&nbsp;px. U&nbsp;popisku je rozptyl je\u0161t\u011b v\u011bt\u0161\u00ed: re\u00e1ln\u011b\n        se vykresluje kolem 930\u2009\u2013\u2009940&nbsp;px, Mangools uv\u00e1d\u00ed limit 960&nbsp;px, Screaming\n        Frog a\u017e 1005&nbsp;px. Bereme opatrn\u011bj\u0161\u00ed \u010d\u00edsla z&nbsp;tohoto rozp\u011bt\u00ed.&nbsp;<\/p>\n    <\/div>\n    <div class=\"kp-caveat\">\n      <h2>Na mobilu je limit rozpo\u010det, ne \u0161\u00ed\u0159ka&nbsp;\u0159\u00e1dku<\/h2>\n      <p>\n        Mobiln\u00ed titulek se zalamuje do dvou \u0159\u00e1dk\u016f, Google ho ne\u0159e\u017ee na konci\n        prvn\u00edho. Limit 490&nbsp;px je proto celkov\u00fd rozpo\u010det textu, ne \u0161\u00ed\u0159ka\n        jednoho \u0159\u00e1dku. Proto se titulek se 467&nbsp;px v&nbsp;n\u00e1hledu zalom\u00ed, a&nbsp;p\u0159esto\n        projde. Mobiln\u00ed hodnoty berte sp\u00ed\u0161 orienta\u010dn\u011b ne\u017e jako p\u0159esnou hranici.&nbsp;<\/p>\n    <\/div>\n    <div class=\"kp-caveat\">\n      <h2>Titulek si Google stejn\u011b m\u016f\u017ee p\u0159epsat<\/h2>\n      <p>\n        Zhruba v&nbsp;60&nbsp;% p\u0159\u00edpad\u016f si Google titulek ve v\u00fdsledc\u00edch p\u0159ep\u00ed\u0161e s\u00e1m,\n        a&nbsp;to podle H1, textu odkaz\u016f nebo n\u00e1zvu zna\u010dky. K&nbsp;popisku si nav\u00edc m\u016f\u017ee\n        p\u0159idat datum nebo ztu\u010dnit slova z&nbsp;dotazu, co\u017e ubere p\u00e1r pixel\u016f.\n        N\u00e1stroj v\u00e1m \u0159ekne, jestli se v\u00e1\u0161 n\u00e1vrh vejde, ne jestli ho Google\n        pou\u017eije.&nbsp;<\/p>\n    <\/div>\n    <div class=\"kp-caveat\">\n      <h2>M\u011b\u0159\u00edme v&nbsp;prohl\u00ed\u017ee\u010di<\/h2>\n      <p>\n        Text se vykresluje do skryt\u00e9ho canvasu Googl\u00edm fontem (Arial 20&nbsp;px pro\n        desktopov\u00fd titulek, 18&nbsp;px pro mobiln\u00ed, 14&nbsp;px pro popisek) a&nbsp;m\u011b\u0159\u00ed se\n        jeho skute\u010dn\u00e1 \u0161\u00ed\u0159ka. Nic se nikam neodes\u00edl\u00e1, data z\u016fst\u00e1vaj\u00ed u&nbsp;v\u00e1s\n        v&nbsp;prohl\u00ed\u017ee\u010di. Vykreslov\u00e1n\u00ed p\u00edsma se mezi Windows, Linuxem a&nbsp;macOS\n        li\u0161\u00ed o&nbsp;jednotky pixel\u016f.&nbsp;<\/p>\n    <\/div>\n  <\/section>\n\n  <!-- ---------------- z\u00e1vere\u010dn\u00e9 CTA ----------------\n       Jedin\u00fd odkaz z n\u00e1stroja do funnelu. Text nepred\u00e1va n\u00e1stroj, ale\n       pomenuje, \u010do n\u00e1stroj NEvie, a to je presne obsah auditu. -->\n  <aside class=\"kp-cta\">\n    <div class=\"kp-cta-text\">\n      <h2>Tohle je jen jeden d\u00edlek skl\u00e1da\u010dky<\/h2>\n      <p>\n        Titulek a&nbsp;popisek rozhoduj\u00ed o&nbsp;tom, jestli na v\u00e1\u0161 v\u00fdsledek n\u011bkdo klikne.\n        Jestli se v\u016fbec zobraz\u00edte, rozhoduje zbytek webu. Ten v\u00e1m projdeme\n        v&nbsp;SEO auditu.&nbsp;<\/p>\n    <\/div>\n    <a class=\"kp-cta-btn\" href=\"https:\/\/keypers.io\/cs\/seo-audit\/\">Chci SEO&nbsp;audit<\/a>\n  <\/aside>\n<\/div>\n<\/div>\n\n<script>\n(() => {\n  \"use strict\";\n\n  \/* ==================================================================\n     LIMITS \u2014 the one block worth arguing about.\n\n     Google publishes none of this; every number below is community-derived\n     and shifts whenever Google reflows the SERP. IMPORTANT: a pixel limit is\n     only meaningful paired with the font size it was measured at. These fonts\n     (20px desktop title \/ 18px mobile title \/ 14px desc) are the tool's own\n     measurement fonts, so any limit you set here must be derived by measuring\n     a REAL truncated SERP string in these same sizes \u2014 not borrowed from a\n     tool that measures at 18px\/13px (e.g. Screaming Frog's \"561px\" title is an\n     18px number \u2248 ~620px at 20px). The mobile title especially is a two-line\n     text budget, not a single-line cut, and is the least certain value.\n\n     `warn` is where we start nagging, `limit` is where Google cuts.\n     Tune freely \u2014 the rest of the code reads these and nothing else.\n     ================================================================== *\/\n  const LIMITS = {\n    desktop: {\n      title: { font: \"20px Arial, 'Liberation Sans', sans-serif\", warn: 560, limit: 580 },\n      \/\/ Desc: raised per Michal's real-render testing (descriptions show at\n      \/\/ 930\u2013940 px) and the tools he cited (Mangools cuts at 960, Screaming\n      \/\/ Frog lists 1005). limit=960 (Mangools) is the conservative cut point;\n      \/\/ warn=920 is the old estimate, now the \"you're in contested territory\"\n      \/\/ line. Bump to 1005 if you want to match Screaming Frog.\n      desc:  { font: \"14px Arial, 'Liberation Sans', sans-serif\", warn: 920, limit: 960 }\n    },\n    mobile: {\n      title: { font: \"18px Arial, 'Liberation Sans', sans-serif\", warn: 460, limit: 490 },\n      desc:  { font: \"14px Arial, 'Liberation Sans', sans-serif\", warn: 640, limit: 680 }\n    }\n  };\n\n  const ELLIPSIS = \"\u2026\";\n\n  const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n\n  \/** Width of `text` in CSS pixels when rendered in `font`. *\/\n  const widthOf = (text, font) => {\n    ctx.font = font;\n    return ctx.measureText(text).width;\n  };\n\n  \/** Grapheme-aware segmentation, so emoji count as one and Czech diacritics\n      don't get split into base + combining mark. Used for both counting and\n      truncation, so the two never disagree. *\/\n  const segmenter = typeof Intl !== \"undefined\" && Intl.Segmenter\n    ? new Intl.Segmenter(\"cs\", { granularity: \"grapheme\" })\n    : null;\n  const toGraphemes = (text) =>\n    segmenter ? [...segmenter.segment(text)].map((s) => s.segment) : [...text];\n  const countChars = (text) => toGraphemes(text).length;\n\n  \/**\n   * Split `text` at the point Google would truncate it.\n   * Returns { kept, cut } \u2014 `cut` is \"\" when the whole string fits.\n   * Google backs off to a word boundary and appends an ellipsis.\n   * Works over graphemes (never splits an emoji \/ combining mark) and backs\n   * off on any whitespace, including the U+00A0 non-breaking spaces Czech\n   * puts after one-letter prepositions (v, s, k, z, a, i, o, u).\n   *\/\n  function splitAtTruncation(text, font, limit) {\n    if (!text) return { kept: \"\", cut: \"\" };\n    if (widthOf(text, font) <= limit) return { kept: text, cut: \"\" };\n\n    const budget = limit - widthOf(ELLIPSIS, font);\n    const units = toGraphemes(text);\n\n    \/\/ Binary search for the longest grapheme prefix that fits with the ellipsis.\n    let lo = 0, hi = units.length;\n    while (lo < hi) {\n      const mid = Math.ceil((lo + hi) \/ 2);\n      if (widthOf(units.slice(0, mid).join(\"\"), font) <= budget) lo = mid;\n      else hi = mid - 1;\n    }\n\n    let kept = units.slice(0, lo).join(\"\");\n    \/\/ Back off to the last whitespace, but never to nothing.\n    const m = kept.match(\/[\\s\u00a0][^\\s\u00a0]*$\/);\n    if (m && m.index > 0) kept = kept.slice(0, m.index);\n\n    return { kept, cut: text.slice(kept.length) };\n  }\n\n  const state = (w, spec) =>\n    w > spec.limit ? \"over\" : w >= spec.warn ? \"warn\" : \"ok\";\n\n  const LABEL = { ok: \"Vejde se\", warn: \"Na hran\u011b\", over: \"O\u0159\u00edzne se\" };\n\n  const el = (id) => document.getElementById(id);\n\n  const ui = {\n    title: {\n      input: el(\"kp-in-title\"), out: el(\"kp-out-title\"),\n      fill: el(\"kp-fill-title\"), tick: el(\"kp-tick-title\"),\n      nums: el(\"kp-nums-title\"), pill: el(\"kp-pill-title\"),\n      status: el(\"kp-status-title\"), name: \"Titulek\", prev: null,\n      empty: \"Bez titulku\"\n    },\n    desc: {\n      input: el(\"kp-in-desc\"), out: el(\"kp-out-desc\"),\n      fill: el(\"kp-fill-desc\"), tick: el(\"kp-tick-desc\"),\n      nums: el(\"kp-nums-desc\"), pill: el(\"kp-pill-desc\"),\n      status: el(\"kp-status-desc\"), name: \"Popisek\", prev: null,\n      empty: \"Bez popisku Google vygeneruje \u00faryvek ze str\u00e1nky s\u00e1m.\"\n    }\n  };\n\n  let device = \"desktop\";\n\n  \/** Render one measured field: preview text, ruler, readout, pill. *\/\n  function renderField(key) {\n    const spec = LIMITS[device][key];\n    const node = ui[key];\n    const text = node.input.value.trim();\n\n    const w = text ? widthOf(text, spec.font) : 0;\n    const { kept, cut } = splitAtTruncation(text, spec.font, spec.limit);\n    const st = state(w, spec);\n\n    \/\/ Preview \u2014 truncated tail stays visible but marked as lost. The hatch\n    \/\/ texture (not colour alone) carries \"this is cut\" for sighted users; the\n    \/\/ sr-only wrappers carry it for screen readers.\n    node.out.textContent = \"\";\n    if (!text) {\n      const span = document.createElement(\"span\");\n      span.className = \"kp-snippet-empty\";\n      span.textContent = node.empty;\n      node.out.append(span);\n    } else {\n      node.out.append(document.createTextNode(kept));\n      if (cut) {\n        node.out.append(document.createTextNode(ELLIPSIS));\n        const srOpen = document.createElement(\"span\");\n        srOpen.className = \"kp-sr-only\";\n        srOpen.textContent = \" (o\u0159\u00edznuto: \";\n        node.out.append(srOpen);\n        const ghost = document.createElement(\"span\");\n        ghost.className = \"kp-ghost\";\n        ghost.textContent = cut;\n        node.out.append(ghost);\n        const srClose = document.createElement(\"span\");\n        srClose.className = \"kp-sr-only\";\n        srClose.textContent = \")\";\n        node.out.append(srClose);\n      }\n    }\n\n    \/\/ Ruler \u2014 the track represents the limit exactly.\n    node.fill.style.width = Math.min(w \/ spec.limit, 1) * 100 + \"%\";\n    node.fill.dataset.state = st;\n    node.tick.style.left = (spec.warn \/ spec.limit) * 100 + \"%\";\n\n    \/\/ Readout, assembled from nodes rather than parsed from a string.\n    \/\/ Only numbers are interpolated here, so innerHTML would be safe *today* \u2014\n    \/\/ but this page runs on keypers.io, where script in the page shares an\n    \/\/ origin with a logged-in WordPress session. Keeping the file free of\n    \/\/ HTML-parsing sinks makes that a structural property instead of something\n    \/\/ a later edit can quietly undo by dropping a string in.\n    const px = Math.round(w);\n    const over = px - spec.limit;\n\n    const strong = (v) => {\n      const b = document.createElement(\"b\");\n      b.textContent = v;\n      return b;\n    };\n    const sep = () => {\n      const s = document.createElement(\"span\");\n      s.className = \"kp-sep\";\n      s.textContent = \" \u00b7 \";\n      return s;\n    };\n\n    node.nums.textContent = \"\";\n    node.nums.append(strong(px), `\/${spec.limit} px`, sep(), strong(countChars(text)), \" znak\u016f\");\n    if (over > 0) node.nums.append(sep(), `+${over} px p\u0159es`);\n\n    node.pill.dataset.state = st;\n    node.pill.textContent = LABEL[st];\n\n    \/\/ Screen-reader announcement, only when the state actually changes, so\n    \/\/ typing doesn't flood the live region on every keystroke.\n    if (node.prev !== st) {\n      node.status.textContent = `${node.name}: ${px} z ${spec.limit} pixel\u016f, ${LABEL[st]}.`;\n      node.prev = st;\n    }\n  }\n\n  \/** Google shows brand + breadcrumbs now, not a raw URL. *\/\n  function renderUrl() {\n    const raw = el(\"kp-in-url\").value.trim();\n    let host = \"\", crumbs = [];\n    \/\/ Note: this whole function is deliberately free of any literal \"\/\/\" (and of\n    \/\/ any regex containing two adjacent slashes). An over-eager inline-JS\n    \/\/ minifier (e.g. Breeze) that isn't string\/regex-aware would treat \"\/\/\" as a\n    \/\/ line comment and truncate the line, killing the entire script. SLASH lets\n    \/\/ us build \"\/\/\" at runtime without writing it in the source.\n    const SLASH = \"\/\";\n    let u = null;\n    try { u = new URL(raw); } catch (e) { u = null; }                 \/\/ already has a scheme\n    if (!u) { try { u = new URL(\"https:\" + SLASH + SLASH + raw); } catch (e) { u = null; } }\n    if (u) {\n      host = u.hostname.replace(\/^www\\.\/, \"\");\n      crumbs = u.pathname.split(SLASH).filter(Boolean).map((s) => {\n        try { return decodeURIComponent(s); } catch (e) { return s; }\n      });\n    } else {\n      host = raw.replace(\/^[a-z][\\w+.-]*:\/i, \"\").replace(\/^\\\/+\/, \"\")\n                .replace(\/^www\\.\/, \"\").split(SLASH)[0];\n    }\n\n    const brand = host ? host.split(\".\")[0] : \"\";\n    el(\"kp-site-name\").textContent = brand\n      ? brand.charAt(0).toUpperCase() + brand.slice(1)\n      : \"N\u00e1zev webu\";\n    el(\"kp-favicon\").textContent = (brand.charAt(0) || \"?\").toUpperCase();\n\n    \/\/ Google dropped the path breadcrumb on mobile results in Jan 2025 \u2014\n    \/\/ mobile now shows site name + domain only. Desktop keeps the crumbs.\n    const crumbsToShow = device === \"mobile\" ? [] : crumbs;\n    el(\"kp-site-url\").textContent = host\n      ? [host, ...crumbsToShow].join(\" \u203a \")\n      : \"example.com\";\n  }\n\n  function renderAll() {\n    renderField(\"title\");\n    renderField(\"desc\");\n    renderUrl();\n    el(\"kp-screen\").dataset.device = device;\n    el(\"kp-device-label\").textContent =\n      (device === \"desktop\" ? \"Desktop\" : \"Mobil\") + \" \u00b7 Arial\";\n  }\n\n  \/\/ ---- wiring ----\n  [ui.title.input, ui.desc.input].forEach((n) =>\n    n.addEventListener(\"input\", () => renderField(n === ui.title.input ? \"title\" : \"desc\"))\n  );\n  el(\"kp-in-url\").addEventListener(\"input\", renderUrl);\n\n  const btnD = el(\"kp-btn-desktop\"), btnM = el(\"kp-btn-mobile\");\n  const setDevice = (d) => {\n    device = d;\n    btnD.setAttribute(\"aria-pressed\", String(d === \"desktop\"));\n    btnM.setAttribute(\"aria-pressed\", String(d === \"mobile\"));\n    renderAll();\n  };\n  btnD.addEventListener(\"click\", () => setDevice(\"desktop\"));\n  btnM.addEventListener(\"click\", () => setDevice(\"mobile\"));\n\n  \/\/ Measure once now (Arial is a local font, available immediately). The\n  \/\/ fonts.ready re-render only matters for the Booton UI chrome swapping in;\n  \/\/ the Arial-measured numbers don't change. It never rejects, so a missing\n  \/\/ Booton can't hang it.\n  renderAll();\n  if (document.fonts && document.fonts.ready) document.fonts.ready.then(renderAll);\n})();\n<\/script>\n\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t<\/div>\n","protected":false},"excerpt":{"rendered":"<p>SERP simul\u00e1tor Napi\u0161te titulek a&nbsp;popisek a&nbsp;hned uvid\u00edte, jak se zobraz\u00ed ve v\u00fdsledc\u00edch Googlu a&nbsp;kde je o\u0159\u00edzne.&nbsp; Desktop Mobil Titulek str\u00e1nky &lt;title&gt; Popisek meta description Zjist\u00edme, pro\u010d v\u00e1\u0161 e-shop z organiky nevyd\u011bl\u00e1v\u00e1 tolik, kolik by mohl. Konkr\u00e9tn\u00ed zad\u00e1n\u00ed pro v\u00fdvoj\u00e1\u0159e, ne obecn\u00e1 doporu\u010den\u00ed. 11 let, 350 klient\u016f. URL adresa zobraz\u00ed se jako drobe\u010dky N\u00e1hled Desktop \u00b7&nbsp;Arial [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"_acf":"","inline_featured_image":false,"footnotes":""},"class_list":["post-1996","page","type-page","status-publish","hentry"],"acf":[],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO Pro 4.9.10 - aioseo.com -->\n\t<meta name=\"description\" content=\"SERP simul\u00e1tor Napi\u0161te titulek a popisek a hned uvid\u00edte, jak se zobraz\u00ed ve v\u00fdsledc\u00edch Googlu a kde je o\u0159\u00edzne. Desktop Mobil Titulek str\u00e1nky Popisek meta description Zjist\u00edme, pro\u010d v\u00e1\u0161 e-shop z organiky nevyd\u011bl\u00e1v\u00e1 tolik, kolik by mohl. Konkr\u00e9tn\u00ed zad\u00e1n\u00ed pro v\u00fdvoj\u00e1\u0159e, ne obecn\u00e1 doporu\u010den\u00ed. 11 let, 350 klient\u016f. URL adresa zobraz\u00ed se jako drobe\u010dky N\u00e1hled Desktop \u00b7 Arial\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"google-site-verification\" content=\"6FT1oxYFaWwRgVdupZ10HXBoM2OLHJKQuxXIOhRIZnw\" \/>\n\t<link rel=\"canonical\" href=\"https:\/\/keypers.io\/cs\/serp-simulator\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO Pro (AIOSEO) 4.9.10\" \/>\n\t\t<meta property=\"og:locale\" content=\"cs_CZ\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Keypers\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"SERP simul\u00e1tor | Keypers\" \/>\n\t\t<meta property=\"og:description\" content=\"SERP simul\u00e1tor Napi\u0161te titulek a popisek a hned uvid\u00edte, jak se zobraz\u00ed ve v\u00fdsledc\u00edch Googlu a kde je o\u0159\u00edzne. Desktop Mobil Titulek str\u00e1nky Popisek meta description Zjist\u00edme, pro\u010d v\u00e1\u0161 e-shop z organiky nevyd\u011bl\u00e1v\u00e1 tolik, kolik by mohl. Konkr\u00e9tn\u00ed zad\u00e1n\u00ed pro v\u00fdvoj\u00e1\u0159e, ne obecn\u00e1 doporu\u010den\u00ed. 11 let, 350 klient\u016f. URL adresa zobraz\u00ed se jako drobe\u010dky N\u00e1hled Desktop \u00b7 Arial\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/keypers.io\/cs\/serp-simulator\/\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/keypers.io\/wp-content\/uploads\/navrh-bez-nazvu-2.png\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/keypers.io\/wp-content\/uploads\/navrh-bez-nazvu-2.png\" \/>\n\t\t<meta property=\"og:image:width\" content=\"320\" \/>\n\t\t<meta property=\"og:image:height\" content=\"320\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-07-19T09:24:59+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-07-21T09:32:32+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary\" \/>\n\t\t<meta name=\"twitter:title\" content=\"SERP simul\u00e1tor | Keypers\" \/>\n\t\t<meta name=\"twitter:description\" content=\"SERP simul\u00e1tor Napi\u0161te titulek a popisek a hned uvid\u00edte, jak se zobraz\u00ed ve v\u00fdsledc\u00edch Googlu a kde je o\u0159\u00edzne. Desktop Mobil Titulek str\u00e1nky Popisek meta description Zjist\u00edme, pro\u010d v\u00e1\u0161 e-shop z organiky nevyd\u011bl\u00e1v\u00e1 tolik, kolik by mohl. Konkr\u00e9tn\u00ed zad\u00e1n\u00ed pro v\u00fdvoj\u00e1\u0159e, ne obecn\u00e1 doporu\u010den\u00ed. 11 let, 350 klient\u016f. URL adresa zobraz\u00ed se jako drobe\u010dky N\u00e1hled Desktop \u00b7 Arial\" \/>\n\t\t<meta name=\"twitter:image\" content=\"https:\/\/keypers.io\/wp-content\/uploads\/navrh-bez-nazvu-2.png\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/serp-simulator\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/keypers.io\\\/cs\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/serp-simulator\\\/#listItem\",\"name\":\"SERP simul\\u00e1tor\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/serp-simulator\\\/#listItem\",\"position\":2,\"name\":\"SERP simul\\u00e1tor\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/#listItem\",\"name\":\"Home\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/#organization\",\"name\":\"Keypers\",\"url\":\"https:\\\/\\\/keypers.io\\\/cs\\\/\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/serp-simulator\\\/#webpage\",\"url\":\"https:\\\/\\\/keypers.io\\\/cs\\\/serp-simulator\\\/\",\"name\":\"SERP simul\\u00e1tor | Keypers\",\"description\":\"SERP simul\\u00e1tor Napi\\u0161te titulek a popisek a hned uvid\\u00edte, jak se zobraz\\u00ed ve v\\u00fdsledc\\u00edch Googlu a kde je o\\u0159\\u00edzne. Desktop Mobil Titulek str\\u00e1nky Popisek meta description Zjist\\u00edme, pro\\u010d v\\u00e1\\u0161 e-shop z organiky nevyd\\u011bl\\u00e1v\\u00e1 tolik, kolik by mohl. Konkr\\u00e9tn\\u00ed zad\\u00e1n\\u00ed pro v\\u00fdvoj\\u00e1\\u0159e, ne obecn\\u00e1 doporu\\u010den\\u00ed. 11 let, 350 klient\\u016f. URL adresa zobraz\\u00ed se jako drobe\\u010dky N\\u00e1hled Desktop \\u00b7 Arial\",\"inLanguage\":\"cs-CZ\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/serp-simulator\\\/#breadcrumblist\"},\"datePublished\":\"2026-07-19T11:24:59+02:00\",\"dateModified\":\"2026-07-21T11:32:32+02:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/#website\",\"url\":\"https:\\\/\\\/keypers.io\\\/cs\\\/\",\"name\":\"Keypers\",\"inLanguage\":\"cs-CZ\",\"publisher\":{\"@id\":\"https:\\\/\\\/keypers.io\\\/cs\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO Pro -->\r\n\t\t<title>SERP simul\u00e1tor | Keypers<\/title>\n\n","aioseo_head_json":{"title":"SERP simul\u00e1tor | Keypers","description":"SERP simul\u00e1tor Napi\u0161te titulek a popisek a hned uvid\u00edte, jak se zobraz\u00ed ve v\u00fdsledc\u00edch Googlu a kde je o\u0159\u00edzne. Desktop Mobil Titulek str\u00e1nky Popisek meta description Zjist\u00edme, pro\u010d v\u00e1\u0161 e-shop z organiky nevyd\u011bl\u00e1v\u00e1 tolik, kolik by mohl. Konkr\u00e9tn\u00ed zad\u00e1n\u00ed pro v\u00fdvoj\u00e1\u0159e, ne obecn\u00e1 doporu\u010den\u00ed. 11 let, 350 klient\u016f. URL adresa zobraz\u00ed se jako drobe\u010dky N\u00e1hled Desktop \u00b7 Arial","canonical_url":"https:\/\/keypers.io\/cs\/serp-simulator\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"google-site-verification":"6FT1oxYFaWwRgVdupZ10HXBoM2OLHJKQuxXIOhRIZnw","miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BreadcrumbList","@id":"https:\/\/keypers.io\/cs\/serp-simulator\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/keypers.io\/cs\/#listItem","position":1,"name":"Home","item":"https:\/\/keypers.io\/cs\/","nextItem":{"@type":"ListItem","@id":"https:\/\/keypers.io\/cs\/serp-simulator\/#listItem","name":"SERP simul\u00e1tor"}},{"@type":"ListItem","@id":"https:\/\/keypers.io\/cs\/serp-simulator\/#listItem","position":2,"name":"SERP simul\u00e1tor","previousItem":{"@type":"ListItem","@id":"https:\/\/keypers.io\/cs\/#listItem","name":"Home"}}]},{"@type":"Organization","@id":"https:\/\/keypers.io\/cs\/#organization","name":"Keypers","url":"https:\/\/keypers.io\/cs\/"},{"@type":"WebPage","@id":"https:\/\/keypers.io\/cs\/serp-simulator\/#webpage","url":"https:\/\/keypers.io\/cs\/serp-simulator\/","name":"SERP simul\u00e1tor | Keypers","description":"SERP simul\u00e1tor Napi\u0161te titulek a popisek a hned uvid\u00edte, jak se zobraz\u00ed ve v\u00fdsledc\u00edch Googlu a kde je o\u0159\u00edzne. Desktop Mobil Titulek str\u00e1nky Popisek meta description Zjist\u00edme, pro\u010d v\u00e1\u0161 e-shop z organiky nevyd\u011bl\u00e1v\u00e1 tolik, kolik by mohl. Konkr\u00e9tn\u00ed zad\u00e1n\u00ed pro v\u00fdvoj\u00e1\u0159e, ne obecn\u00e1 doporu\u010den\u00ed. 11 let, 350 klient\u016f. URL adresa zobraz\u00ed se jako drobe\u010dky N\u00e1hled Desktop \u00b7 Arial","inLanguage":"cs-CZ","isPartOf":{"@id":"https:\/\/keypers.io\/cs\/#website"},"breadcrumb":{"@id":"https:\/\/keypers.io\/cs\/serp-simulator\/#breadcrumblist"},"datePublished":"2026-07-19T11:24:59+02:00","dateModified":"2026-07-21T11:32:32+02:00"},{"@type":"WebSite","@id":"https:\/\/keypers.io\/cs\/#website","url":"https:\/\/keypers.io\/cs\/","name":"Keypers","inLanguage":"cs-CZ","publisher":{"@id":"https:\/\/keypers.io\/cs\/#organization"}}]},"og:locale":"cs_CZ","og:site_name":"Keypers","og:type":"article","og:title":"SERP simul\u00e1tor | Keypers","og:description":"SERP simul\u00e1tor Napi\u0161te titulek a popisek a hned uvid\u00edte, jak se zobraz\u00ed ve v\u00fdsledc\u00edch Googlu a kde je o\u0159\u00edzne. Desktop Mobil Titulek str\u00e1nky Popisek meta description Zjist\u00edme, pro\u010d v\u00e1\u0161 e-shop z organiky nevyd\u011bl\u00e1v\u00e1 tolik, kolik by mohl. Konkr\u00e9tn\u00ed zad\u00e1n\u00ed pro v\u00fdvoj\u00e1\u0159e, ne obecn\u00e1 doporu\u010den\u00ed. 11 let, 350 klient\u016f. URL adresa zobraz\u00ed se jako drobe\u010dky N\u00e1hled Desktop \u00b7 Arial","og:url":"https:\/\/keypers.io\/cs\/serp-simulator\/","og:image":"https:\/\/keypers.io\/wp-content\/uploads\/navrh-bez-nazvu-2.png","og:image:secure_url":"https:\/\/keypers.io\/wp-content\/uploads\/navrh-bez-nazvu-2.png","og:image:width":320,"og:image:height":320,"article:published_time":"2026-07-19T09:24:59+00:00","article:modified_time":"2026-07-21T09:32:32+00:00","twitter:card":"summary","twitter:title":"SERP simul\u00e1tor | Keypers","twitter:description":"SERP simul\u00e1tor Napi\u0161te titulek a popisek a hned uvid\u00edte, jak se zobraz\u00ed ve v\u00fdsledc\u00edch Googlu a kde je o\u0159\u00edzne. Desktop Mobil Titulek str\u00e1nky Popisek meta description Zjist\u00edme, pro\u010d v\u00e1\u0161 e-shop z organiky nevyd\u011bl\u00e1v\u00e1 tolik, kolik by mohl. Konkr\u00e9tn\u00ed zad\u00e1n\u00ed pro v\u00fdvoj\u00e1\u0159e, ne obecn\u00e1 doporu\u010den\u00ed. 11 let, 350 klient\u016f. URL adresa zobraz\u00ed se jako drobe\u010dky N\u00e1hled Desktop \u00b7 Arial","twitter:image":"https:\/\/keypers.io\/wp-content\/uploads\/navrh-bez-nazvu-2.png"},"aioseo_meta_data":{"post_id":"1996","title":null,"description":null,"keywords":null,"keyphrases":{"focus":{"keyphrase":"","score":0,"analysis":{"keyphraseInTitle":{"score":0,"maxScore":9,"error":1}}},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"WebPage","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":null,"frequency":"default","local_seo":null,"seo_analyzer_scan_date":"2026-07-21 09:35:04","breadcrumb_settings":null,"limit_modified_date":false,"open_ai":null,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"created":"2026-07-16 11:30:40","updated":"2026-07-21 09:35:04"},"_links":{"self":[{"href":"https:\/\/keypers.io\/cs\/wp-json\/wp\/v2\/pages\/1996","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/keypers.io\/cs\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/keypers.io\/cs\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/keypers.io\/cs\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/keypers.io\/cs\/wp-json\/wp\/v2\/comments?post=1996"}],"version-history":[{"count":10,"href":"https:\/\/keypers.io\/cs\/wp-json\/wp\/v2\/pages\/1996\/revisions"}],"predecessor-version":[{"id":2015,"href":"https:\/\/keypers.io\/cs\/wp-json\/wp\/v2\/pages\/1996\/revisions\/2015"}],"wp:attachment":[{"href":"https:\/\/keypers.io\/cs\/wp-json\/wp\/v2\/media?parent=1996"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}