// GS1 Sunrise 2027 explainer page (gs1-sunrise-2027.html).
//
// A long-form, plain-English guide written to answer "What is GS1 Sunrise 2027?" for people and for
// search / AI engines. Facts are drawn from GS1 publications (see the Sources section at the bottom).
//
// SUNRISE_UPDATED is shown on the page and used by tools/seo.js for the Article dates, so change it
// whenever the content is revised. SUNRISE_FAQ is rendered at the bottom of the page and tools/seo.js
// turns the same array into FAQPage markup, so the markup always matches what visitors can read.
// Answer blocks: { p: "paragraph" } or { ul: [ "item" | ["Bold label", "text"] ] }.

const SUNRISE_UPDATED = "November 4, 2026";

const SUNRISE_FAQ = [
  {
    q: "What is GS1 Sunrise 2027?",
    a: [
      { p: "GS1 Sunrise 2027 is a global industry initiative for retail checkout systems to be able to scan and process both traditional 1D barcodes and GS1-enabled 2D barcodes by the end of 2027." },
      { p: "It was set by the retail industry in collaboration with GS1, the organization that maintains the barcode standards used on products worldwide. The aim is that a product with a GS1-enabled 2D barcode can be recognized at the checkout in the same way a traditional barcode is today." },
    ],
  },
  {
    q: "When is GS1 Sunrise 2027?",
    a: [
      { p: "GS1’s plan aims for retail checkout systems to be able to scan and process 2D barcodes by the end of 2027. Retailers are moving at different speeds, so readiness varies from one retailer to the next. Brands are generally advised to start preparing before 2027 and to keep the traditional barcode on packaging during the transition." },
    ],
  },
  {
    q: "Is GS1 Sunrise 2027 mandatory?",
    a: [
      { p: "Sunrise 2027 is an industry timeline set by retailers and brands together with GS1. It is not a government regulation. Individual retailers decide what they ask of their suppliers and when, so the practical answer depends on who you sell to. Some regulated sectors, such as healthcare, have their own rules for 2D barcodes." },
    ],
  },
  {
    q: "Is the UPC or EAN barcode going away?",
    a: [
      { p: "GS1’s plan does not remove the traditional UPC or EAN barcode. During the transition, a package can carry both the traditional barcode and a 2D barcode (known as dual marking). GS1’s guidance says that until about 90% of checkout scanning solutions can read GS1-compliant 2D barcodes and capture the product number, a product with a 2D barcode on its pack should also carry a traditional barcode. Once 2D barcodes are widely supported at the checkout, brands can choose to use a 2D barcode on its own." },
    ],
  },
  {
    q: "What is the difference between a regular QR code and a GS1 Digital Link QR code?",
    a: [
      { p: "A regular QR code can hold any web address. A QR code that uses GS1 Digital Link follows a GS1 standard: the web address is built around the product’s GTIN (its Global Trade Item Number), so a checkout can pick out the product number while a shopper’s phone follows the link." },
      { p: "That is what lets one code do two jobs. A QR code that does not include the GTIN in the GS1 format is not a GS1-enabled 2D barcode." },
    ],
  },
  {
    q: "What information can a 2D barcode carry?",
    a: [
      { p: "A traditional barcode carries only the product number. A GS1-enabled 2D barcode can carry that number plus additional data, for example:" },
      { ul: [
        ["Batch or lot number,", "so a recall can focus on the affected lots."],
        ["Expiry or best-before date,", "so retailers can sell or promote products nearing their date first."],
        ["Serial number,", "for traceability and product authentication."],
        ["A web link,", "so a shopper who scans with a phone camera can reach product information, instructions, recycling guidance and more."],
      ] },
    ],
  },
  {
    q: "What countries does GS1 Sunrise 2027 apply to?",
    a: [
      { p: "GS1 Sunrise 2027 is a global initiative, not a rule for a single country. The goal is for retail checkout systems around the world to be able to read GS1 2D barcodes by the end of 2027. Each country has its own GS1 member organization that supports local brands and retailers with the change." },
      { ul: [
        ["United States,", "GS1 US describes Sunrise 2027 as a global transition from 1D UPC barcodes to 2D barcodes and publishes guidance for brands and retailers."],
        ["Canada,", "GS1 Canada describes the move to 2D barcodes as one of the biggest changes since the original introduction of the linear barcode."],
        ["Other countries,", "the same global goal applies, with guidance coming through each country’s GS1 member organization."],
        ["Specific sectors,", "some sectors have their own timelines, such as cannabis and pharmacy in Canada."],
      ] },
      { p: "How quickly the change happens depends on the country and on each retailer, so it is worth asking your retail partners and your local GS1 member organization where they stand." },
    ],
  },
  {
    q: "Do I need new product numbers (GTINs) for a 2D barcode?",
    a: [
      { p: "No. A 2D barcode uses the same GTIN that identifies your product today. If you already have products in stores with a UPC or EAN barcode, you already have the product numbers you need. They are issued through GS1 membership." },
    ],
  },
  {
    q: "How can TapScan help with GS1 Sunrise 2027?",
    a: [
      { p: "TapScan helps brands plan the move from 1D barcodes to GS1-enabled 2D barcodes, create branded QR codes using GS1 Digital Link, connect them to product identities and destinations, and deliver the right information for each scan. Content can be updated over time without reprinting packaging, and engagement can be measured." },
    ],
  },
];

function srRenderBlocks(blocks) {
  return blocks.map((b, i) => b.ul ? (
    <ul key={i}>
      {b.ul.map((li, j) => (
        <li key={j}>{Array.isArray(li) ? <React.Fragment><strong>{li[0]}</strong>{" "}{li[1]}</React.Fragment> : li}</li>
      ))}
    </ul>
  ) : <p key={i}>{b.p}</p>);
}

// House style puts an amber full stop after headings; question headings keep their question mark instead.
// One collapsible question. Same behaviour as the homepage DPI questions: the answer height is measured so it
// expands fully, and the answer stays in the page HTML while collapsed (data-prerender-keep keeps it in the
// saved crawler copy), so search engines and AI crawlers can read every answer.
function SrFaqItem({ q, a, i, isOpen, onToggle }) {
  const bodyRef = React.useRef(null);
  const [maxH, setMaxH] = React.useState(0);
  React.useEffect(() => {
    const el = bodyRef.current;
    if (!el) return;
    const measure = () => setMaxH(el.scrollHeight);
    measure();
    window.addEventListener("resize", measure);
    let ro;
    if ("ResizeObserver" in window) { ro = new ResizeObserver(measure); ro.observe(el); }
    return () => { window.removeEventListener("resize", measure); if (ro) ro.disconnect(); };
  }, [isOpen]);
  // Anywhere in the box toggles it. Two exceptions: a click on a link, and the click that ends selecting text in the answer.
  const handleClick = (e) => {
    if (e.target.closest && e.target.closest("a")) return;
    const sel = window.getSelection ? String(window.getSelection()) : "";
    if (sel.length > 0 && e.target.closest && e.target.closest(".ts-sr-acc-a")) return;
    onToggle();
  };
  return (
    <div className={"ts-sr-acc-item" + (isOpen ? " is-open" : "")} onClick={handleClick}>
      <h3 className="ts-sr-acc-h">
        <button type="button" className="ts-sr-acc-q" id={"sr-faq-q-" + i} aria-expanded={isOpen} aria-controls={"sr-faq-a-" + i}>
          <span className="ts-sr-acc-q-text">{q}</span>
          <span className="ts-sr-acc-icon" aria-hidden="true">+</span>
        </button>
      </h3>
      <div id={"sr-faq-a-" + i} role="region" aria-labelledby={"sr-faq-q-" + i} aria-hidden={!isOpen} data-prerender-keep className="ts-sr-acc-a" style={{
        maxHeight: isOpen ? maxH : 0, visibility: isOpen ? "visible" : "hidden",
        transition: "max-height 360ms cubic-bezier(0.2,0.8,0.2,1), visibility 0s linear " + (isOpen ? "0s" : "360ms"),
      }}>
        <div ref={bodyRef} className="ts-sr-acc-a-in">{srRenderBlocks(a)}</div>
      </div>
    </div>
  );
}

// Counts up from 0 to `to` the first time it scrolls into view. The final number is laid out invisibly underneath
// so the box does not change size while counting. With reduced motion (or no IntersectionObserver) it just shows
// the final number. It is decorative (aria-hidden): the figure is also written out in the sentence beside it.
function SrCountUp({ to, suffix = "%", duration = 1600 }) {
  const ref = React.useRef(null);
  const [val, setVal] = React.useState(0);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce || !("IntersectionObserver" in window)) { setVal(to); return; }
    let raf = 0, started = false;
    const run = () => {
      const t0 = performance.now();
      const tick = (now) => {
        const p = Math.min(1, (now - t0) / duration);
        setVal(Math.round(to * (1 - Math.pow(1 - p, 3))));
        if (p < 1) raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
    };
    const io = new IntersectionObserver((entries) => {
      if (!started && entries.some((e) => e.isIntersecting)) { started = true; io.disconnect(); run(); }
    }, { threshold: 0.6 });
    io.observe(el);
    return () => { io.disconnect(); if (raf) cancelAnimationFrame(raf); };
  }, [to, duration]);
  return (
    <span ref={ref} className="ts-sr-stat-num" aria-hidden="true">
      <span style={{ visibility: "hidden" }}>{to}{suffix}</span>
      <span style={{ position: "absolute", left: 0, top: 0 }}>{val}{suffix}</span>
    </span>
  );
}

// Eased scroll to a page position (used by the "On this page" links). Longer trips take a little longer but never
// more than about a second. Any scroll input from the visitor (wheel, touch, keys) cancels it. With reduced
// motion it jumps straight there.
let srScrollRaf = 0;
function srSmoothScrollTo(top) {
  if (srScrollRaf) cancelAnimationFrame(srScrollRaf);
  const from = window.pageYOffset, dist = top - from;
  const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (reduce || Math.abs(dist) < 2) { window.scrollTo(0, top); return; }
  const duration = Math.min(1000, Math.max(500, 350 + Math.abs(dist) * 0.18));
  const t0 = performance.now();
  const stop = () => { if (srScrollRaf) cancelAnimationFrame(srScrollRaf); srScrollRaf = 0; cleanup(); };
  const cleanup = () => ["wheel", "touchstart", "keydown", "mousedown"].forEach((ev) => window.removeEventListener(ev, stop));
  ["wheel", "touchstart", "keydown", "mousedown"].forEach((ev) => window.addEventListener(ev, stop, { passive: true }));
  const tick = (now) => {
    const p = Math.min(1, (now - t0) / duration);
    const eased = p < 0.5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2; // easeInOutCubic
    window.scrollTo(0, from + dist * eased);
    if (p < 1) srScrollRaf = requestAnimationFrame(tick); else { srScrollRaf = 0; cleanup(); }
  };
  srScrollRaf = requestAnimationFrame(tick);
}

// Annotated diagrams for the "How GS1 Digital Link works" section. Facts follow the GS1 Digital Link URI Syntax standard:
// the path holds the GTIN (AI 01) then, optionally, batch/lot (AI 10) then serial number (AI 21), in that order; data such as
// the expiry date (AI 17, YYMMDD) goes in the query string after a "?"; any domain can be used; the GTIN is always 14 digits.
const SR_LINK_PARTS = [
  { code: "https://brand.example", tag: "Web address", color: "#B9C0F5", pos: "below" },
  { code: "/01/09506000134352", tag: "Product number (GTIN)", color: "#FFB855", pos: "above" },
  { code: "/10/LOT42", tag: "Batch or lot", color: "#8BE2B0", pos: "below", optional: true },
  { code: "/21/12345", tag: "Serial number", color: "#9ED8FF", pos: "above", optional: true },
  { code: "?17=261231", tag: "Expiry date", color: "#F6A9D2", pos: "below", optional: true },
];
// 0 + 9506000 + 13435 + 2 = the same 14-digit GTIN as above
const SR_GTIN_PARTS = [
  { code: "0", tag: "Leading zero", color: "#B9C0F5", pos: "below" },
  { code: "9506000", tag: "GS1 Company Prefix", color: "#FFB855", pos: "above" },
  { code: "13435", tag: "Item reference", color: "#8BE2B0", pos: "below" },
  { code: "2", tag: "Check digit", color: "#9ED8FF", pos: "above" },
];
// The code reads as one unbroken line, with colour marking each part. Tags alternate below and above the line (pos) so they
// never crowd each other. On narrow screens the line becomes one card per part instead (see the media query).
function SrUrlDiagram({ parts, label, big }) {
  return (
    <div className={"ts-sr-url-diagram" + (big ? " is-big" : "")} role="group" aria-label={label}>
      {parts.map((p, i) => (
        <div className={"ts-sr-urlpart" + (p.code.length <= 2 ? " is-point" : "")} data-pos={p.pos} key={i} style={{ "--c": p.color }}>
          <code className="ts-sr-urlpart-code">{p.code}</code>
          <span className="ts-sr-urlpart-bracket" aria-hidden="true" />
          <span className="ts-sr-urlpart-tagwrap">
            <span className="ts-sr-urlpart-tag">{p.tag}</span>
            <span className="ts-sr-urlpart-opt">{p.optional ? "Optional" : ""}</span>
          </span>
        </div>
      ))}
    </div>
  );
}

function SrHeading({ id, eyebrow, children }) {
  const isQuestion = typeof children === "string" && /\?$/.test(children);
  return (
    <React.Fragment>
      {eyebrow ? <p className="ts-sr-eyebrow">{eyebrow}</p> : null}
      <h2 id={id} className="ts-sr-h2">{children}{isQuestion ? null : <span style={{ color: "#FFB855" }}>.</span>}</h2>
    </React.Fragment>
  );
}

// Button tracking. Every button and link on this page records an outbound hit against the visit's campaign hit
// (components/campaign-hit.js), labelled "GS1 Sunrise 2027: <where on the page>: <what was clicked>". The label is
// exactly what a dashboard chart matches on, so keep the wording stable. A visit with no campaign hit yet queues it.
function srTrack(label) {
  try { if (window.tsRecordOutboundHit) window.tsRecordOutboundHit("GS1 Sunrise 2027: " + label); } catch (err) {}
}

function SrExternal({ href, children }) {
  return <a href={href} target="_blank" rel="noopener noreferrer" onClick={() => srTrack("Sources: " + children)}>{children}</a>;
}

const SR_TOC = [
  ["what-it-is", "What it means"],
  ["why", "Why the industry is moving to 2D"],
  ["1d-vs-2d", "1D vs 2D barcodes"],
  ["gs1-2d-barcodes", "What a GS1-enabled 2D barcode is"],
  ["timeline", "The timeline"],
  ["who", "Who is affected"],
  ["myths", "Common misunderstandings"],
  ["prepare", "How to prepare"],
  ["faq", "FAQ"],
];

function SunrisePage() {
  // All questions start closed; opening one closes the others. -1 means none open.
  const [openFaq, setOpenFaq] = React.useState(-1);
  const onDemo = () => window.open("https://calendly.com/katie-aotc/20min", "_blank", "noopener");
  const onLogin = () => window.open("https://tapscan-platform.vercel.app/", "_blank", "noopener");
  const onContact = () => { window.location.href = "contact.html"; };
  const onLegal = (label) => {
    const slug = { "Privacy": "privacy", "Terms & Conditions": "terms", "Acceptable Use": "acceptable-use" }[label];
    window.location.href = "legal/" + slug + ".html";
  };

  // Header and footer (shared components): one listener records any button or link clicked in them, labelled by its own
  // text. It listens in the capture phase so the hit is sent before a click that leaves the page does its work.
  React.useEffect(() => {
    const onClick = (e) => {
      const el = e.target && e.target.closest ? e.target.closest("a, button") : null;
      if (!el) return;
      const zone = el.closest(".ts-nav") ? "Header" : el.closest("footer") ? "Footer" : null;
      if (!zone) return;
      const text = (el.textContent || "").replace(/\s+/g, " ").trim() || el.getAttribute("aria-label") || "Logo";
      srTrack(zone + ": " + text);
    };
    document.addEventListener("click", onClick, true);
    return () => document.removeEventListener("click", onClick, true);
  }, []);

  // "On this page" links: glide to the section (heading just below the fixed nav) instead of jumping.
  const goToSection = (e, id) => {
    const el = document.getElementById(id);
    if (!el) return;
    e.preventDefault();
    const nav = document.querySelector(".ts-nav");
    const navH = nav ? nav.getBoundingClientRect().height : 72;
    const anchor = el.querySelector(".ts-sr-wrap") || el;
    srSmoothScrollTo(Math.max(0, Math.round(anchor.getBoundingClientRect().top + window.pageYOffset - navH - 28)));
    try { history.replaceState(null, "", "#" + id); } catch (err) {}
  };

  // Scroll reveal. Each element below gets data-rv="0" (hidden) and flips to "1" the first time it scrolls into
  // view, which plays the rise-and-fade in the CSS above. Items in a group (cards, steps, list rows, questions)
  // are staggered. Skipped entirely for reduced motion or if IntersectionObserver is missing, so content is never
  // left hidden. React does not manage data-* attributes, so re-renders (e.g. opening a question) leave them alone.
  React.useLayoutEffect(() => {
    const root = document.querySelector(".ts-sr");
    const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (!root || reduce || !("IntersectionObserver" in window)) return;
    const els = Array.from(root.querySelectorAll([
      ".ts-sr-intro .ts-sr-answer", ".ts-sr-hero .ts-sr-toc",
      "main .ts-sr-wrap > .ts-sr-eyebrow", "main .ts-sr-wrap > .ts-sr-h2", "main .ts-sr-wrap > .ts-sr-h3", "main .ts-sr-wrap > p",
      "main .ts-sr-wrap > ul > li", "main .ts-sr-wrap > ol > li",
      "main .ts-sr-cards > .ts-sr-card", "main .ts-sr-type-cards > .ts-sr-type-card", "main .ts-sr-steps > .ts-sr-step",
      "main .ts-sr-wrap > .ts-sr-figure", "main .ts-sr-wrap > .ts-sr-url-diagram", "main .ts-sr-wrap > .ts-sr-table-wrap", "main .ts-sr-wrap > .ts-sr-stat",
      "main .ts-sr-wrap > .ts-sr-note", "main .ts-sr-wrap > .ts-sr-rule", "main .ts-sr-wrap > .ts-sr-help", "main .ts-sr-acc > .ts-sr-acc-item", "main .ts-sr-end > div",
    ].join(",")));
    const groupSel = ".ts-sr-card, .ts-sr-type-card, .ts-sr-step, .ts-sr-acc-item, .ts-sr-end > div, li";
    const counts = new Map();
    els.forEach((el) => {
      let d = 0;
      if (el.matches(groupSel)) {
        const n = counts.get(el.parentElement) || 0;
        counts.set(el.parentElement, n + 1);
        d = Math.min(n, 6) * (el.matches(".ts-sr-step") ? 140 : 90);
      } else if (el.matches(".ts-sr-h2")) d = 70;
      else if (el.matches("p") && el.previousElementSibling && el.previousElementSibling.matches(".ts-sr-h2")) d = 140;
      el.style.setProperty("--rv-d", d + "ms");
      el.setAttribute("data-rv", "0");
    });
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { e.target.setAttribute("data-rv", "1"); io.unobserve(e.target); } });
    }, { threshold: 0.12, rootMargin: "0px 0px -6% 0px" });
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  }, []);

  // A link like gs1-sunrise-2027.html#faq should land on the section once React has drawn the page.
  React.useEffect(() => {
    const id = decodeURIComponent((window.location.hash || "").slice(1));
    if (!id) return;
    const t = setTimeout(() => { const el = document.getElementById(id); if (el) el.scrollIntoView(); }, 150);
    return () => clearTimeout(t);
  }, []);

  return (
    <div className="ts-sr" style={{ fontFamily: "Montserrat", background: "#fff", color: "#212331" }}>
      <style>{`
        .ts-sr a { color: #2F3886; }
        .ts-sr a:hover { color: #1C2250; }
        .ts-sr-hero { position: relative; overflow: hidden; isolation: isolate; color: #fff; text-align: center; padding: 190px 24px 96px; background: linear-gradient(to bottom left, #6976E7 0%, #2F3886 100%); }
        .ts-sr-hero::before, .ts-sr-hero::after { content: ""; position: absolute; inset: -20%; z-index: 0; pointer-events: none; }
        .ts-sr-hero::before { background: radial-gradient(closest-side at 30% 30%, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0) 60%), radial-gradient(closest-side at 70% 80%, rgba(105,118,231,0.55) 0%, rgba(105,118,231,0) 65%); background-size: 140% 140%, 140% 140%; background-repeat: no-repeat; animation: tsSrDriftA 28s ease-in-out infinite alternate; }
        .ts-sr-hero::after { background: radial-gradient(closest-side at 80% 25%, rgba(255,184,85,0.10) 0%, rgba(255,184,85,0) 65%), radial-gradient(closest-side at 20% 75%, rgba(28,34,80,0.30) 0%, rgba(28,34,80,0) 65%); background-size: 160% 160%, 160% 160%; background-repeat: no-repeat; animation: tsSrDriftB 36s ease-in-out infinite alternate; mix-blend-mode: soft-light; }
        .ts-sr-hero > * { position: relative; z-index: 1; }
        @keyframes tsSrDriftA { 0% { background-position: 0% 0%, 100% 100%; } 50% { background-position: 40% 20%, 60% 80%; } 100% { background-position: 100% 100%, 0% 0%; } }
        @keyframes tsSrDriftB { 0% { background-position: 100% 0%, 0% 100%; } 50% { background-position: 60% 40%, 40% 60%; } 100% { background-position: 0% 100%, 100% 0%; } }
        @media (prefers-reduced-motion: reduce) { .ts-sr-hero::before, .ts-sr-hero::after { animation: none; } }
        .ts-sr-pill { display: inline-block; font-weight: 700; font-size: 14px; letter-spacing: 0.01em; padding: 8px 18px; border-radius: 999px; background: rgba(255,255,255,0.16); border: 1px solid rgba(255,255,255,0.35); margin-bottom: 26px; }
        .ts-sr-h1 { font-weight: 900; line-height: 1.05; margin: 0; letter-spacing: -0.02em; color: #fff; font-size: clamp(34px, 5.4vw, 56px); text-shadow: 4px 4px 4px rgba(0,0,0,0.25), -4px -4px 4px rgba(0,0,0,0.25); }
        .ts-sr-sub { font-weight: 300; font-size: clamp(17px, 2.2vw, 22px); line-height: 1.45; margin: 26px auto 0; max-width: 780px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); }
        .ts-sr-meta { margin: 24px 0 0; font-size: 13px; font-weight: 500; opacity: 0.85; }
        .ts-sr-wrap { max-width: 880px; margin: 0 auto; padding: 0 24px; }
        .ts-sr-intro { background: #F5F6FF; padding: 64px 0 72px; }
        /* "On this page": sits in the hero, centred, as glass-style pills on the gradient */
        .ts-sr-toc { max-width: 940px; margin: 76px auto 0; padding-top: 60px; border-top: 1px solid rgba(255,255,255,0.22); }
        .ts-sr-toc-head { display: flex; justify-content: center; align-items: baseline; flex-wrap: wrap; gap: 4px 14px; margin: 0 0 22px; }
        .ts-sr-toc-title { margin: 0; font-weight: 700; font-size: 17px; line-height: 1.3; color: #fff; text-shadow: 2px 2px 4px rgba(0,0,0,0.25); }
        .ts-sr-toc-hint { font-weight: 300; font-size: 14px; letter-spacing: 0.01em; color: rgba(255,255,255,0.72); }
        .ts-sr-toc ol { margin: 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 12px; }
        .ts-sr-toc a { display: inline-block; padding: 10px 18px; border-radius: 999px; background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.34); font-weight: 600; font-size: 14px; text-decoration: none; color: #fff; -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); transition: background 160ms, border-color 160ms, color 160ms, transform 160ms; }
        .ts-sr-toc a:hover { background: #fff; border-color: #fff; color: #2F3886; transform: translateY(-2px); }
        .ts-sr-toc a:focus-visible { outline: 3px solid #FFB855; outline-offset: 3px; }
        .ts-sr-answer { background: #fff; border: 1px solid #DDDFEE; border-left: 6px solid #FFB855; border-radius: 14px; padding: 28px 32px; box-shadow: 2px 2px 4px rgba(139,146,205,0.25); }
        /* Buttons reuse the footer's .ts-cta-btn behaviour (lift, shimmer sweep, sliding arrow, colour flip). No underline on hover. */
        .ts-sr .ts-sr-answer-cta, .ts-sr .ts-sr-btn { display: inline-flex; align-items: center; justify-content: center; gap: 12px; font-family: Montserrat, sans-serif; font-weight: 700; line-height: 1.3; border-radius: 10px; cursor: pointer; text-decoration: none !important; }
        .ts-sr .ts-sr-answer-cta:hover, .ts-sr .ts-sr-btn:hover, .ts-sr .ts-sr-answer-cta:focus, .ts-sr .ts-sr-btn:focus { text-decoration: none !important; }
        .ts-sr .ts-sr-answer-cta { margin-top: 30px; padding: 15px 28px; font-size: 16px; background: #FFB855; color: #1C2250; border: none; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18); }
        .ts-sr .ts-sr-answer-cta.ts-cta-btn-primary:hover, .ts-sr .ts-sr-btn.ts-cta-btn-primary:hover { background: #E99623 !important; border-color: #E99623 !important; color: #1C2250 !important; box-shadow: 0 18px 38px rgba(233, 150, 35, 0.38); }
        .ts-sr .ts-sr-answer-cta:focus-visible { outline: 3px solid #1C2250; outline-offset: 3px; }
        .ts-sr .ts-sr-btn { padding: 15px 28px; font-size: 15px; }
        .ts-sr .ts-sr-btn.ts-cta-btn-primary { background: #FFB855; color: #1C2250; border: 2px solid #FFB855; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18); }
        .ts-sr .ts-sr-btn.ts-cta-btn-secondary { background: transparent; color: #fff; border: 2px solid #fff; }
        .ts-sr .ts-sr-btn:focus-visible { outline: 3px solid #FFB855; outline-offset: 3px; }
        .ts-sr .ts-sr-answer-cta svg, .ts-sr .ts-sr-btn svg, .ts-sr .ts-sr-answer-cta .ts-cta-arrow, .ts-sr .ts-sr-btn .ts-cta-arrow { flex: none; }
        .ts-sr .ts-sr-answer-cta span:not(.ts-cta-arrow), .ts-sr .ts-sr-btn span:not(.ts-cta-arrow) { text-wrap: balance; }
        .ts-sr-answer-label { position: relative; margin: 0 0 22px; padding-bottom: 14px; font-weight: 800; font-size: 22px; line-height: 1.2; letter-spacing: -0.005em; color: #1C2250; }
        .ts-sr-answer-label::after { content: ""; position: absolute; left: 0; bottom: 0; width: 40px; height: 3px; border-radius: 3px; background: linear-gradient(90deg, #FFB855, #6976E7); }
        .ts-sr-answer p { margin: 0; font-weight: 600; font-size: 19px; line-height: 1.6; color: #212331; }
        .ts-sr-answer p + p { margin-top: 14px; font-weight: 500; font-size: 16px; line-height: 1.7; }
        .ts-sr-sec { padding: 80px 0; scroll-margin-top: 100px; background: #fff; }
        .ts-sr-sec.is-tint { background: #F5F6FF; }
        .ts-sr-sec + .ts-sr-sec { border-top: 1px solid #E3E6F4; }
        .ts-sr-eyebrow { display: inline-block; margin: 0 0 16px !important; font-weight: 700 !important; font-size: 13.5px !important; line-height: 1 !important; letter-spacing: 0.01em; color: #2F3886; background: #E8EBFF; border: 1px solid #D3D8F8; border-radius: 999px; padding: 8px 16px; }
        .ts-sr-sec.is-tint .ts-sr-eyebrow { background: #fff; border-color: #D3D8F8; }
        .ts-sr-h2 { position: relative; font-weight: 900; font-size: clamp(25px, 3.2vw, 34px); line-height: 1.15; letter-spacing: -0.01em; margin: 0 0 30px; padding-bottom: 20px; color: #1C2250; scroll-margin-top: 100px; }
        .ts-sr-h2::after { content: ""; position: absolute; left: 0; bottom: 0; width: 56px; height: 4px; border-radius: 4px; background: linear-gradient(90deg, #FFB855, #6976E7); }
        .ts-sr-h2.is-plain { padding-bottom: 0; }
        .ts-sr-h2.is-plain::after { display: none; }
        .ts-sr-sec .ts-sr-wrap > h2 + p { font-size: 18px; font-weight: 600; line-height: 1.7; color: #1C2250; }
        .ts-sr-h3 { font-weight: 800; font-size: 21px; line-height: 1.3; margin: 48px 0 14px; padding-top: 32px; border-top: 1px solid #DDDFEE; color: #2F3886; }
        main p { margin: 0 0 18px; font-weight: 500; font-size: 16.5px; line-height: 1.75; }
        .ts-sr ul.ts-sr-list, .ts-sr .ts-sr-acc ul { margin: 0 0 20px; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 12px; }
        .ts-sr ul.ts-sr-list li, .ts-sr .ts-sr-acc li { position: relative; padding-left: 22px; font-weight: 500; font-size: 16.5px; line-height: 1.7; }
        .ts-sr ul.ts-sr-list li::before, .ts-sr .ts-sr-acc li::before { content: ""; position: absolute; left: 3px; top: 11px; width: 7px; height: 7px; border-radius: 999px; background: #FFB855; }
        [id="1d-vs-2d"] .ts-sr-wrap > h2 + p { margin-bottom: 36px; }
        .ts-sr-figure { margin: 0 0 44px; padding-bottom: 40px; border-bottom: 1px solid #DDDFEE; }
        .ts-sr-figure img { display: block; width: 100%; height: auto; border-radius: 16px; border: 1px solid #DDDFEE; }
        .ts-sr-figure figcaption { margin-top: 14px; font-size: 14px; font-style: italic; font-weight: 500; color: #7A7D8F; line-height: 1.5; }
        .ts-sr-table-wrap { overflow-x: auto; margin: 6px 0 22px; border: 1px solid #DDDFEE; border-radius: 14px; }
        .ts-sr-table { width: 100%; table-layout: fixed; border-collapse: collapse; min-width: 560px; background: #fff; }
        .ts-sr-table th, .ts-sr-table td { text-align: left; vertical-align: top; padding: 15px 18px; font-size: 15px; line-height: 1.55; border-bottom: 1px solid #E8EAF5; }
        .ts-sr-table thead th { background: #2F3886; color: #fff; font-weight: 700; border-bottom: none; }
        .ts-sr-table thead th:nth-child(1) { width: 29%; }
        .ts-sr-table thead th:nth-child(2) { width: 30%; }
        .ts-sr-table thead th:nth-child(3) { width: 41%; }
        .ts-sr-table tbody th { font-weight: 800; background: #F5F6FF; vertical-align: middle; color: #1C2250; }
        .ts-sr-rowh { display: flex; align-items: center; gap: 14px; }
        .ts-sr-rowicon { flex: none; width: 40px; height: 40px; border-radius: 12px; display: grid; place-items: center; background: #fff; border: 1px solid #D3D8F8; color: #2F3886; }
        .ts-sr-table tbody td { vertical-align: middle; text-wrap: pretty; }
        .ts-sr-table tbody td:nth-of-type(1) { background: #fff; }
        .ts-sr-table tbody td:last-child { background: #FFF6EA; }
        .ts-sr-table td { font-weight: 500; }
        .ts-sr-table tbody tr:last-child th, .ts-sr-table tbody tr:last-child td { border-bottom: none; }
        .ts-sr-type-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px; margin: 32px 0 0; }
        .ts-sr-type-card { display: flex; flex-direction: column; background: #fff; border: 1px solid #DDDFEE; border-top: 4px solid #FFB855; border-radius: 14px; padding: 26px 22px 22px; box-shadow: 2px 2px 4px rgba(139,146,205,0.18); }
        .ts-sr-type-card.is-yes { border-top-color: #2EAD6B; }
        .ts-sr-type-card.is-partial { border-top-color: #FFB855; }
        .ts-sr-type-card.is-no { border-top-color: #B9BDD0; }
        .ts-sr-type-card h3 { margin: 0 0 14px; min-height: 44px; font-weight: 800; font-size: 17px; line-height: 1.3; color: #2F3886; }
        .ts-sr-type-card p { margin: 0; font-size: 15px; line-height: 1.7; }
        .ts-sr-type-card .ts-sr-badge-row { margin-top: auto; padding-top: 22px; }
        .ts-sr-type-card .ts-sr-badge { display: flex; align-items: center; gap: 11px; width: 100%; min-height: 54px; border-radius: 12px; font-size: 13px; padding: 8px 14px; }
        .ts-sr-url-diagram { display: flex; justify-content: center; align-items: center; background: #1C2250; border-radius: 16px; padding: 112px 32px; margin: 8px 0 30px; overflow-x: auto; }
        .ts-sr-url-diagram.is-big { padding: 96px 32px; }
        .ts-sr-urlpart { position: relative; flex: none; }
        .ts-sr-urlpart-code { display: block; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 17px; font-weight: 600; line-height: 1.6; white-space: nowrap; color: var(--c); }
        .ts-sr-url-diagram.is-big .ts-sr-urlpart-code { font-size: 34px; letter-spacing: 0.06em; }
        .ts-sr-urlpart-bracket { position: absolute; left: 2px; right: 2px; height: 12px; border: 2px solid var(--c); opacity: 0.85; }
        .ts-sr-urlpart-bracket::after { content: ""; position: absolute; left: 50%; width: 2px; height: 14px; margin-left: -1px; background: var(--c); }
        .ts-sr-urlpart[data-pos="below"] .ts-sr-urlpart-bracket { top: calc(100% + 6px); border-top: none; border-radius: 0 0 9px 9px; }
        .ts-sr-urlpart[data-pos="below"] .ts-sr-urlpart-bracket::after { top: 100%; }
        .ts-sr-urlpart[data-pos="above"] .ts-sr-urlpart-bracket { bottom: calc(100% + 6px); border-bottom: none; border-radius: 9px 9px 0 0; }
        .ts-sr-urlpart[data-pos="above"] .ts-sr-urlpart-bracket::after { bottom: 100%; }
        .ts-sr-urlpart.is-point .ts-sr-urlpart-bracket { height: 0; border: none; }
        .ts-sr-urlpart.is-point[data-pos="below"] .ts-sr-urlpart-bracket::after { top: 0; height: 26px; }
        .ts-sr-urlpart.is-point[data-pos="above"] .ts-sr-urlpart-bracket::after { bottom: 0; height: 26px; }
        .ts-sr-urlpart-tagwrap { position: absolute; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 8px; white-space: nowrap; }
        .ts-sr-urlpart[data-pos="below"] .ts-sr-urlpart-tagwrap { top: calc(100% + 32px); flex-direction: column; }
        .ts-sr-urlpart[data-pos="above"] .ts-sr-urlpart-tagwrap { bottom: calc(100% + 32px); flex-direction: column-reverse; }
        .ts-sr-urlpart-tag { padding: 6px 14px; border-radius: 999px; font-weight: 700; font-size: 13px; line-height: 1.3; color: var(--c); background: #262F66; border: 1px solid var(--c); }
        .ts-sr-urlpart-opt { font-size: 12.5px; font-style: italic; font-weight: 300; letter-spacing: 0.01em; color: rgba(255,255,255,0.72); }
        .ts-sr-urlpart-opt:empty { display: none; }
        .ts-sr ul.ts-sr-list.ts-sr-dots li::before { width: 10px; height: 10px; left: 2px; top: 10px; background: var(--dot, #FFB855); box-shadow: 0 0 0 1px rgba(28, 34, 80, 0.16); }
        .ts-sr-badge svg { flex: none; }
        .ts-sr-rule { border: none; border-top: 1px solid #DDDFEE; margin: 48px 0 44px; }
        .ts-sr-note.has-mark { display: flex; align-items: stretch; gap: 26px; margin-top: 0; }
        .ts-sr-note-mark { flex: none; display: flex; align-items: center; padding-right: 26px; border-right: 1px solid rgba(28, 34, 80, 0.22); color: #1C2250; }
        .ts-sr-note-mark svg { display: block; }
        .ts-sr-note-body { min-width: 0; }
        .ts-sr-badge-row { display: block; margin-top: 10px; }
        .ts-sr-badge { display: inline-block; padding: 5px 13px; border-radius: 999px; font-weight: 700; font-size: 13px; line-height: 1.3; letter-spacing: 0.01em; }
        .ts-sr-badge.is-yes { background: #E3F6EA; color: #14633A; border: 1px solid #A9DFC0; }
        .ts-sr-badge.is-partial { background: #FFE9C2; color: #7A4500; border: 1px solid #FFD58A; }
        .ts-sr-badge.is-no { background: #EDEEF3; color: #5A5F77; border: 1px solid #DDDFEA; }
        .ts-sr-note { background: #FFF6EA; border-left: 4px solid #FFB855; border-radius: 14px; padding: 22px 26px; margin: 48px 0 0; }
        #prepare .ts-sr-note { margin-top: 64px; }
        /* "Getting ready" sits on the brand gold tint instead of the usual blue tint */
        #prepare.ts-sr-sec { background: #FFF6EA; }
        #prepare.ts-sr-sec, #prepare + .ts-sr-sec { border-top-color: #F6E3C4; }
        #prepare .ts-sr-eyebrow { border-color: #FFDDA6; }
        #prepare .ts-sr-steps-list li { border-bottom-color: #F0D9B0; }
        #prepare .ts-sr-steps-list li:last-child { border-bottom: none; }
        #prepare .ts-sr-note { background: #fff; box-shadow: 0 2px 10px rgba(180, 120, 20, 0.08); }
        .ts-sr-note p { margin: 0; font-size: 15.5px; line-height: 1.7; }
        .ts-sr-note .ts-sr-note-title { margin: 0 0 8px; font-size: 19px; font-weight: 800; line-height: 1.3; color: #1C2250; }
        .ts-sr-note strong { font-weight: 800; }
        .ts-sr-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin: 8px 0 22px; }
        .ts-sr-step { background: #F5F6FF; border: 1px solid #DDDFEE; border-radius: 14px; padding: 22px; }
        .ts-sr-step-tag { display: inline-block; font-weight: 800; font-size: 13.5px; letter-spacing: 0.01em; color: #1C2250; background: #FFB855; border-radius: 999px; padding: 6px 12px; margin-bottom: 14px; }
        .ts-sr-step h3 { font-weight: 800; font-size: 18px; margin: 0 0 8px; line-height: 1.3; }
        .ts-sr-step p { margin: 0; font-size: 15px; line-height: 1.65; }
        .ts-sr-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; margin: 8px 0 8px; }
        .ts-sr-card-icon { width: 50px; height: 50px; border-radius: 14px; display: grid; place-items: center; margin-bottom: 20px; }
        .ts-sr-card-icon svg { width: 26px; height: 26px; }
        .ts-sr-card-icon.ts-sr-ic-1 { background: #FFB855; color: #1C2250; }
        .ts-sr-card-icon.ts-sr-ic-2 { background: #6976E7; color: #fff; }
        .ts-sr-card-icon.ts-sr-ic-3 { background: #2F3886; color: #fff; }
        .ts-sr-codeicon { display: block; width: 28px; height: 28px; background-color: currentColor; -webkit-mask: var(--ci) center / contain no-repeat; mask: var(--ci) center / contain no-repeat; }
        .ts-sr-codeicon.is-qr { --ci: url("assets/gs1-qr-icon.svg"); }
        .ts-sr-codeicon.is-dm { --ci: url("assets/gs1-datamatrix-icon.svg"); }
        .ts-sr-card-icon.ts-sr-ic-type { width: 62px; height: 62px; border-radius: 16px; }
        .ts-sr-card-icon.ts-sr-ic-type .ts-sr-codeicon { width: 38px; height: 38px; }
        .ts-sr-card-icon.ts-sr-ic-type.is-yes { background: #E3F6EA; color: #14633A; border: 1px solid #A9DFC0; }
        .ts-sr-card-icon.ts-sr-ic-type.is-partial { background: #FFE9C2; color: #7A4500; border: 1px solid #FFD58A; }
        .ts-sr-card-icon.ts-sr-ic-type.is-no { background: #EDEEF3; color: #5A5F77; border: 1px solid #DDDFEA; }
        .ts-sr-card-icon.ts-sr-ic-blue { background: #6976E7; color: #fff; }
        .ts-sr-card-icon.ts-sr-ic-4 { background: #E8EBFF; color: #2F3886; }
        .ts-sr-card.is-top { border-top: 4px solid #6976E7; padding-top: 20px; }
        .ts-sr-stat { display: flex; align-items: center; gap: 28px; background: #fff; border: 1px solid #DDDFEE; border-left: 6px solid #FFB855; border-radius: 14px; padding: 24px 30px; margin-top: 26px; box-shadow: 2px 2px 4px rgba(139,146,205,0.18); }
        .ts-sr-stat-num { position: relative; display: inline-block; font-variant-numeric: tabular-nums; flex: none; font-weight: 900; font-size: 64px; line-height: 1; letter-spacing: -0.03em; color: #2F3886; }
        main .ts-sr-stat p { margin: 0; font-size: 16.5px; font-weight: 600; line-height: 1.6; }
        .ts-sr-card { background: #fff; border: 1px solid #DDDFEE; border-radius: 14px; padding: 22px 24px; box-shadow: 2px 2px 4px rgba(139,146,205,0.18); }
        .ts-sr-card h3 { font-weight: 800; font-size: 18px; margin: 0 0 8px; line-height: 1.3; color: #2F3886; }
        .ts-sr-card p { margin: 0; font-size: 15.5px; line-height: 1.65; }
        .ts-sr-steps-list { margin: 0; padding: 0; list-style: none; counter-reset: sr-step; display: flex; flex-direction: column; gap: 22px; }
        .ts-sr-ruled { margin: 0; padding: 0; list-style: none; border-top: 1px solid #DDDFEE; }
        .ts-sr ul.ts-sr-ruled li { position: relative; padding: 22px 0 22px 46px; border-bottom: 1px solid #DDDFEE; font-weight: 500; font-size: 16.5px; line-height: 1.7; }
        .ts-sr ul.ts-sr-ruled li::before { content: ""; position: absolute; left: 0; top: 21px; width: 30px; height: 30px; border-radius: 999px; background: #FFB855 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231C2250' stroke-width='3.2' stroke-linecap='round'%3E%3Cpath d='M6.5 6.5l11 11M17.5 6.5l-11 11'/%3E%3C/svg%3E") center / 20px 20px no-repeat; }
        .ts-sr-ruled strong { display: block; color: #1C2250; font-weight: 800; margin-bottom: 4px; }
        .ts-sr-steps-list li { counter-increment: sr-step; position: relative; padding: 0 0 22px 56px; border-bottom: 1px solid #DDDFEE; font-weight: 500; font-size: 16.5px; line-height: 1.7; }
        .ts-sr-steps-list li:last-child { border-bottom: none; padding-bottom: 0; }
        .ts-sr-steps-list li strong { color: #1C2250; font-weight: 800; }
        .ts-sr-steps-list li::before { content: counter(sr-step); position: absolute; left: 0; top: 0; width: 34px; height: 34px; border-radius: 999px; background: #FFB855; color: #1C2250; font-weight: 800; font-size: 15px; line-height: 34px; text-align: center; }
        .ts-sr-help { background: linear-gradient(to bottom left, #6976E7 0%, #2F3886 100%); color: #fff; border-radius: 20px; padding: 40px 40px 36px; }
        .ts-sr-help h2 { color: #fff; margin-bottom: 16px; }
        .ts-sr-help p { color: #fff; opacity: 0.96; }
        .ts-sr ul.ts-sr-help-list { margin: 26px 0 0; gap: 14px; }
        .ts-sr .ts-sr-help-list li { color: #fff; }
        .ts-sr .ts-sr-help-list li::before { background: #FFB855; }
        .ts-sr-help-actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 24px; }
        .ts-sr-acc { display: flex; flex-direction: column; gap: 20px; }
        .ts-sr-acc-item { background: #fff; border: 1.465px solid #E3E6F4; border-radius: 11.723px; padding: 18px 22px; cursor: pointer; transition: background 180ms cubic-bezier(0.2,0.8,0.2,1), border-color 180ms, box-shadow 180ms; }
        .ts-sr-acc-item.is-open { border-color: #F0C98A; box-shadow: 0 6px 18px rgba(233,150,35,0.16); }
        .ts-sr-acc-h { margin: 0; font: inherit; }
        .ts-sr-acc-q { display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; margin: 0; padding: 0; border: 0; background: none; font: inherit; color: #212331; text-align: left; cursor: pointer; border-radius: 6px; }
        .ts-sr-acc-q:focus-visible { outline: 3px solid #1C2250; outline-offset: 6px; }
        .ts-sr-acc-q-text { font-weight: 700; font-size: 17px; line-height: 1.4; color: #212331; }
        .ts-sr-acc-icon { width: 28px; height: 28px; border-radius: 999px; flex-shrink: 0; display: grid; place-items: center; font-weight: 900; font-size: 16px; line-height: 1; background: #FFB855; color: #212331; transition: transform 180ms cubic-bezier(0.2,0.8,0.2,1), background 180ms, color 180ms; }
        .ts-sr-acc-a { overflow: hidden; }
        .ts-sr-acc-a-in { padding-top: 6px; }
        .ts-sr-acc-a-in p { margin: 14px 0 0; font-weight: 500; font-size: 15.5px; line-height: 1.7; color: #212331; }
        .ts-sr-acc-a-in ul { margin-top: 14px; }
        .ts-sr .ts-sr-acc .ts-sr-acc-a-in li { font-size: 15.5px; }
        .ts-sr-acc-item.is-open .ts-sr-acc-icon { background: #1C2250; color: #fff; transform: rotate(45deg); }
        @media (hover: hover) {
          .ts-sr-acc-item:hover { border-color: #A9AFDB; }
          .ts-sr-acc-item.is-open:hover { border-color: #F0C98A; }
        }
        @media (prefers-reduced-motion: reduce) { .ts-sr-acc-item, .ts-sr-acc-icon, .ts-sr-acc-a { transition: none !important; } }
        /* "Why is the industry moving to 2D barcodes?" — roomier than the other sections */
        #why.ts-sr-sec { padding: 112px 0; }
        #why .ts-sr-eyebrow { margin-bottom: 26px !important; }
        #why .ts-sr-h2 { margin-bottom: 44px; padding-bottom: 28px; }
        #why .ts-sr-wrap > p { margin-bottom: 48px; }
        #why .ts-sr-wrap > h2 + p { margin-bottom: 32px; line-height: 1.8; }
        #why .ts-sr-cards { gap: 32px; margin: 0; }
        #why .ts-sr-card.is-top { padding: 36px 36px 38px; }
        #why .ts-sr-card h3 { margin-bottom: 16px; }
        #why .ts-sr-card p { line-height: 1.8; }
        #why .ts-sr-stat { margin-top: 56px; padding: 40px 44px; gap: 44px; }
        /* "What is the GS1 Sunrise 2027 timeline?" — same roomy spacing, and each step has its own tint (cool blue to warm cream) so the progression reads left to right */
        #timeline.ts-sr-sec { padding: 112px 0; }
        #timeline .ts-sr-eyebrow { margin-bottom: 26px !important; }
        #timeline .ts-sr-h2 { margin-bottom: 44px; padding-bottom: 28px; }
        #timeline .ts-sr-steps { gap: 32px; margin: 0 0 56px; }
        #timeline .ts-sr-step { padding: 36px 36px 40px; }
        #timeline .ts-sr-step:nth-child(1) { background: #E8EBFF; border-color: #D3D8F8; }
        #timeline .ts-sr-step:nth-child(2) { background: #F3EFF8; border-color: #E2D9EF; }
        #timeline .ts-sr-step:nth-child(3) { background: #FFF3E0; border-color: #FFE0A8; }
        #timeline .ts-sr-step-tag { margin-bottom: 24px; }
        #timeline .ts-sr-step h3 { margin-bottom: 16px; }
        #timeline .ts-sr-step p { line-height: 1.8; }
        #timeline .ts-sr-wrap > p { margin-bottom: 0; line-height: 1.8; }
        /* Scroll reveal: elements rise a few pixels and fade in as they enter the screen (set up by the effect in SunrisePage) */
        @keyframes tsSrRise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
        @keyframes tsSrBar { from { width: 0; } to { width: 56px; } }
        [data-rv="0"] { opacity: 0; }
        [data-rv="1"] { animation: tsSrRise 800ms cubic-bezier(0.2, 0.8, 0.2, 1) backwards; animation-delay: var(--rv-d, 0ms); }
        .ts-sr-h2[data-rv="0"]::after { width: 0; }
        .ts-sr-h2[data-rv="1"]::after { animation: tsSrBar 900ms cubic-bezier(0.2, 0.8, 0.2, 1) backwards; animation-delay: calc(var(--rv-d, 0ms) + 280ms); }
        @media (prefers-reduced-motion: reduce), print {
          [data-rv] { opacity: 1 !important; animation: none !important; }
          .ts-sr-h2[data-rv]::after { width: 56px !important; animation: none !important; }
        }
        @media (max-width: 900px), (hover: none) {
          .ts-sr-toc a { display: inline-flex; align-items: center; min-height: 44px; }
          .ts-sr-related { gap: 0; }
          .ts-sr-related a { display: block; padding: 10px 0; }
          .ts-sr .ts-sr-sources.ts-sr-list { gap: 0; }
          .ts-sr .ts-sr-sources.ts-sr-list li a { display: block; padding: 10px 0; }
          .ts-sr .ts-sr-sources.ts-sr-list li::before { top: 21px; }
        }
        @media (min-width: 481px) and (max-width: 900px) {
          .ts-sr-url-diagram.is-big { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 24px; }
        }
        .ts-sr-end { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
        .ts-sr-end h2 { margin-bottom: 18px; font-size: 22px; }
        .ts-sr-end > div + div { border-left: 1px solid #DDDFEE; padding-left: 32px; }
        .ts-sr-related { display: flex; flex-direction: column; gap: 12px; margin: 0 0 20px; padding: 0; list-style: none; }
        .ts-sr-related a { font-weight: 700; font-size: 16.5px; }
        .ts-sr-sources { font-size: 15px; }
        @media (max-width: 860px) {
          .ts-sr-end { grid-template-columns: 1fr; gap: 8px; }
          .ts-sr-end > div + div { border-left: none; padding-left: 0; border-top: 1px solid #DDDFEE; padding-top: 32px; margin-top: 24px; }
        }
        .ts-sr-sources li { font-size: 15px !important; }
        @media (max-width: 860px) { .ts-sr-steps { grid-template-columns: 1fr; } .ts-sr-type-cards { grid-template-columns: 1fr; } .ts-sr-type-card h3 { min-height: 0; } }
        @media (max-width: 900px) {
          .ts-sr-url-diagram, .ts-sr-url-diagram.is-big { flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 16px; padding: 24px 20px; overflow-x: visible; }
          .ts-sr-urlpart { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; padding-left: 14px; border-left: 4px solid var(--c); }
          .ts-sr-urlpart-bracket { display: none; }
          .ts-sr-urlpart-tagwrap, .ts-sr-urlpart[data-pos] .ts-sr-urlpart-tagwrap { display: contents; }
          .ts-sr-urlpart-tag { order: 1; background: transparent; }
          .ts-sr-urlpart-opt { order: 2; }
          .ts-sr-urlpart-code, .ts-sr-url-diagram.is-big .ts-sr-urlpart-code { order: 3; flex-basis: 100%; white-space: normal; word-break: break-all; font-size: 15px; letter-spacing: 0; }
          .ts-sr-url-diagram.is-big .ts-sr-urlpart-code { font-size: 22px; letter-spacing: 0.04em; }
        }
        @media (max-width: 720px) {
          .ts-sr-table-wrap { overflow: visible; border: none; border-radius: 0; }
          .ts-sr-table { min-width: 0; background: none; }
          .ts-sr-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
          .ts-sr-table, .ts-sr-table tbody, .ts-sr-table tr, .ts-sr-table th, .ts-sr-table td { display: block; width: auto; }
          .ts-sr-table tbody tr { background: #fff; border: 1px solid #DDDFEE; border-radius: 14px; margin-bottom: 49px; position: relative; }
          .ts-sr-table tbody tr:last-child { margin-bottom: 0; }
          .ts-sr-table tbody tr th { border-radius: 13px 13px 0 0; }
          .ts-sr-table tbody tr td:last-child { border-radius: 0 0 13px 13px; }
          .ts-sr-table tbody tr:not(:last-child)::after { content: ""; position: absolute; left: 0; right: 0; bottom: -25px; height: 1px; background: #DDDFEE; }
          .ts-sr-table tbody th { width: auto; padding: 12px 16px; border-bottom: 1px solid #DDDFEE; }
          .ts-sr-table td { padding: 12px 16px; border-bottom: 1px solid #E8EAF5; }
          .ts-sr-table tbody tr:last-child th, .ts-sr-table tbody tr:last-child td { border-bottom: 1px solid #E8EAF5; }
          .ts-sr-table tbody tr td:last-child { border-bottom: none; }
          .ts-sr-table td::before { content: attr(data-label); display: block; font-weight: 800; font-size: 12.5px; letter-spacing: 0.01em; color: #6976E7; margin-bottom: 3px; }
        }
        @media (max-width: 720px) {
          .ts-sr-hero { padding: 150px 20px 80px; }
          .ts-sr-wrap { padding: 0 20px; }
          .ts-sr-intro { padding: 36px 0 48px; }
          .ts-sr-toc { margin-top: 56px; padding-top: 44px; }
          .ts-sr-toc a { padding: 8px 14px; font-size: 13px; }
          .ts-sr .ts-sr-answer-cta { display: flex; width: 100%; gap: 9px; padding: 14px 10px; font-size: 13.5px; }
          .ts-sr-note.has-mark { gap: 18px; }
          .ts-sr-note-mark { padding-right: 18px; }
          .ts-sr-note-mark svg { width: 38px; height: 38px; }
          .ts-sr-answer { padding: 22px 20px; }
          .ts-sr-answer p { font-size: 17px; }
          .ts-sr-sec { padding: 56px 0; }
          .ts-sr-acc-item { padding: 16px 18px; }
          .ts-sr-acc-q-text { font-size: 16px; }
          #why.ts-sr-sec { padding: 72px 0; }
          #why .ts-sr-eyebrow { margin-bottom: 20px !important; }
          #why .ts-sr-h2 { margin-bottom: 32px; padding-bottom: 22px; }
          #why .ts-sr-wrap > p { margin-bottom: 32px; }
          #why .ts-sr-wrap > h2 + p { margin-bottom: 24px; }
          #why .ts-sr-cards { gap: 22px; }
          #why .ts-sr-card.is-top { padding: 26px 24px 28px; }
          #why .ts-sr-stat { margin-top: 36px; padding: 26px 24px; gap: 14px; }
          #timeline.ts-sr-sec { padding: 72px 0; }
          #timeline .ts-sr-eyebrow { margin-bottom: 20px !important; }
          #timeline .ts-sr-h2 { margin-bottom: 32px; padding-bottom: 22px; }
          #timeline .ts-sr-steps { gap: 22px; margin-bottom: 36px; }
          #timeline .ts-sr-step { padding: 26px 24px 28px; }
          #timeline .ts-sr-step-tag { margin-bottom: 18px; }
          .ts-sr-h3 { margin-top: 36px; padding-top: 24px; }
          .ts-sr-sec .ts-sr-wrap > h2 + p { font-size: 17px; }
          .ts-sr-stat { flex-direction: column; align-items: flex-start; gap: 10px; padding: 20px; }
          .ts-sr-stat-num { font-size: 52px; }
          .ts-sr-cards { grid-template-columns: 1fr; }
          .ts-sr-help { padding: 30px 22px; }
          .ts-sr .ts-sr-btn { display: flex; width: 100%; text-align: center; }
          main p, .ts-sr ul.ts-sr-list li, .ts-sr-steps-list li { font-size: 16px; }
        }
      `}</style>

      <window.TSHeader variant="onPhoto"
        onNav={(id) => { if (id === "top") window.location.href = "index.html"; else window.location.href = "index.html#" + id; }}
        onContact={onContact}
        onLogin={onLogin}
        onDemo={onDemo} />

      {/* HERO */}
      <section className="ts-sr-hero">
        <div className="ts-sr-pill">Guide to 2D barcodes</div>
        <h1 className="ts-sr-h1">What Is GS1 Sunrise 2027?</h1>
        <p className="ts-sr-sub">A guide to the retail industry’s move from traditional barcodes to 2D barcodes, what it means for brands, and how to prepare.</p>
        <p className="ts-sr-meta">By TapScan &nbsp;·&nbsp; Last updated {SUNRISE_UPDATED}</p>
        <nav className="ts-sr-toc" aria-label="On this page">
          <div className="ts-sr-toc-head">
            <p className="ts-sr-toc-title">On this page</p>
            <span className="ts-sr-toc-hint" aria-hidden="true"><span className="ts-hint-hover">Click to jump to section</span><span className="ts-hint-tap">Tap to jump to section</span></span>
          </div>
          <ol>
            {SR_TOC.map(([id, label]) => <li key={id}><a href={"#" + id} onClick={(e) => { srTrack("On This Page: " + label); goToSection(e, id); }}>{label}</a></li>)}
          </ol>
        </nav>
      </section>

      {/* SHORT ANSWER + CONTENTS */}
      <section className="ts-sr-intro">
        <div className="ts-sr-wrap">
          <div className="ts-sr-answer">
            <h2 className="ts-sr-answer-label">The short answer</h2>
            <p>GS1 Sunrise 2027 is a global industry initiative for retail checkout systems to be able to scan and process both traditional 1D barcodes and GS1-enabled 2D barcodes by the end of 2027.</p>
            <p>In practice, the barcode on a package is expected to grow from a code that holds only a product number into one that can also carry more product data and open a web page when a shopper scans it with a phone. The traditional UPC or EAN barcode is not being switched off: the two are expected to sit side by side during the transition.</p>
            <a className="ts-sr-answer-cta ts-cta-btn ts-cta-btn-primary" href="https://calendly.com/katie-aotc/20min" target="_blank" rel="noopener noreferrer" onClick={() => srTrack("Short Answer: Let's Chat")}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="16" rx="2" /><line x1="3" y1="9.5" x2="21" y2="9.5" /><line x1="8" y1="2.5" x2="8" y2="6.5" /><line x1="16" y1="2.5" x2="16" y2="6.5" /></svg>
              <span>Ready to make the transition? Let’s Chat</span>
              <span className="ts-cta-arrow" aria-hidden="true" style={{ display: "inline-flex" }}><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="12" x2="20" y2="12" /><polyline points="14 6 20 12 14 18" /></svg></span>
            </a>
          </div>
        </div>
      </section>

      <main>
        {/* WHAT IT MEANS */}
        <section className="ts-sr-sec" id="what-it-is">
          <div className="ts-sr-wrap">
            <SrHeading eyebrow="01 · The basics">What does GS1 Sunrise 2027 mean?</SrHeading>
            <p>GS1 is the not-for-profit global standards organization behind the barcodes on almost every product sold in stores, including the UPC in North America and the EAN in Europe and many other regions. In Canada, the GS1 member organization is GS1 Canada.</p>
            <p>In barcode industry language, a “sunrise date” is the point from which a change is expected to be supported. Sunrise 2027 is the plan, set by the retail industry in collaboration with GS1, for retail checkout systems to be ready to read 2D barcodes in addition to the barcodes they read today.</p>
            <p>The first step asked of a checkout is modest. It needs to scan the 2D barcode and extract the product’s GTIN (Global Trade Item Number), the number that identifies the product, in the same way it does with a traditional barcode today. GS1 US notes that retailers do not need to implement every capability of 2D barcodes by that date; more can be added over time.</p>
          </div>
        </section>

        {/* WHY */}
        <section className="ts-sr-sec is-tint" id="why">
          <div className="ts-sr-wrap">
            <SrHeading eyebrow="02 · Why it’s changing">Why is the industry moving to 2D barcodes?</SrHeading>
            <p>The barcode on most packages was designed in the 1970s to do one thing: identify the product at the checkout. A traditional 1D barcode holds a product number and very little else.</p>
            <p>Shoppers, retailers and regulators are all asking for more product information than that. A 2D barcode holds far more in the same space, so a single code can do several jobs at once:</p>
            <div className="ts-sr-cards">
              <div className="ts-sr-card is-top">
                <div className="ts-sr-card-icon ts-sr-ic-blue" aria-hidden="true"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="7" y="2.5" width="10" height="19" rx="2.5" /><path d="M11 18.5h2" /></svg></div>
                <h3>Better information for shoppers</h3>
                <p>A web link lets a shopper scan with their phone camera and reach product details, ingredients and allergen information, safety alerts, recycling guidance and offers, none of which fit on a printed label.</p>
              </div>
              <div className="ts-sr-card is-top">
                <div className="ts-sr-card-icon ts-sr-ic-blue" aria-hidden="true"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><path d="M12 7.5v5.5" /><circle cx="12" cy="16.6" r="0.7" fill="currentColor" /></svg></div>
                <h3>Faster, more targeted recalls</h3>
                <p>A batch or lot number in the code means a recall can focus on the affected lots instead of a whole product line, and a recalled item can be flagged at the checkout so it is not sold. This depends on the lot data being connected to the right systems.</p>
              </div>
              <div className="ts-sr-card is-top">
                <div className="ts-sr-card-icon ts-sr-ic-blue" aria-hidden="true"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M7 19H4.815a1.83 1.83 0 0 1-1.57-.881 1.785 1.785 0 0 1-.004-1.784L7.196 9.5" /><path d="M11 19h8.203a1.83 1.83 0 0 0 1.556-.89 1.784 1.784 0 0 0 0-1.775l-1.226-2.12" /><path d="m14 16-3 3 3 3" /><path d="M8.293 13.596 7.196 9.5 3.1 10.598" /><path d="m9.344 5.811 1.093-1.892A1.83 1.83 0 0 1 11.985 3a1.784 1.784 0 0 1 1.546.888l3.943 6.843" /><path d="m13.378 9.633 4.096 1.098 1.097-4.096" /></svg></div>
                <h3>Less waste, fresher shelves</h3>
                <p>An expiry or best-before date in the code lets retailers sell or promote products nearing their date first and rotate stock more accurately, so fewer expired items are thrown away.</p>
              </div>
              <div className="ts-sr-card is-top">
                <div className="ts-sr-card-icon ts-sr-ic-blue" aria-hidden="true"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M10 10h4" /><path d="M19 7V4a1 1 0 0 0-1-1h-2a1 1 0 0 0-1 1v3" /><path d="M20 21a2 2 0 0 0 2-2v-3.851c0-1.39-2-2.962-2-4.829V8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v11a2 2 0 0 0 2 2z" /><path d="M22 16H2" /><path d="M4 21a2 2 0 0 1-2-2v-3.851c0-1.39 2-2.962 2-4.829V8a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v11a2 2 0 0 1-2 2z" /><path d="M9 7V4a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1v3" /></svg></div>
                <h3>Traceability and authentication</h3>
                <p>Batch, lot and serial data help brands and retailers follow products through the supply chain, and serial numbers can help confirm that a product is genuine.</p>
              </div>
            </div>
            <div className="ts-sr-stat">
              <SrCountUp to={79} />
              <p>Shoppers are interested in this. GS1 US reports that a 2025 consumer survey found that 79% of consumers are more likely to buy a product that has a scannable QR code giving them more information.</p>
            </div>
          </div>
        </section>

        {/* 1D vs 2D */}
        <section className="ts-sr-sec" id="1d-vs-2d">
          <div className="ts-sr-wrap">
            <SrHeading eyebrow="03 · Comparison">1D barcodes vs 2D barcodes</SrHeading>
            <p>A traditional barcode holds a product number. A GS1-enabled 2D barcode holds that same product number and can carry more, including a link a shopper can open with their phone.</p>
            <figure className="ts-sr-figure">
              <img src="assets/gs1-1d-to-2d.webp" width="1600" height="500" loading="lazy" decoding="async" alt="A traditional 1D barcode with an arrow pointing to a 2D QR code" />
              <figcaption>A traditional 1D barcode transitioning to a 2D barcode</figcaption>
            </figure>
            <div className="ts-sr-table-wrap">
              <table className="ts-sr-table">
                <caption style={{ position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }}>Comparison of traditional 1D barcodes and GS1-enabled 2D barcodes</caption>
                <thead>
                  <tr><th scope="col"></th><th scope="col">Traditional 1D barcode</th><th scope="col">GS1-enabled 2D barcode</th></tr>
                </thead>
                <tbody>
                  <tr><th scope="row"><span className="ts-sr-rowh"><span className="ts-sr-rowicon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.9 6.8 19.6l1-5.8L3.5 9.7l5.9-.9L12 3.5z" /></svg></span><span>Examples</span></span></th><td data-label="Traditional 1D barcode">UPC, EAN</td><td data-label="GS1-enabled 2D barcode">QR Code with GS1 Digital Link, GS1 DataMatrix</td></tr>
                  <tr><th scope="row"><span className="ts-sr-rowh"><span className="ts-sr-rowicon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><path d="M14 14h3v3h-3zM20 14h1M14 20h1M18 18h3v3h-3z" /></svg></span><span>What the code contains</span></span></th><td data-label="Traditional 1D barcode">The product number (GTIN) only</td><td data-label="GS1-enabled 2D barcode">The GTIN plus extra data such as batch or lot, dates and serial number</td></tr>
                  <tr><th scope="row"><span className="ts-sr-rowh"><span className="ts-sr-rowicon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M7 9V3h10v6" /><rect x="3" y="9" width="18" height="8" rx="2" /><rect x="7" y="14" width="10" height="7" rx="1" /><circle cx="17.5" cy="12" r="0.6" fill="currentColor" /></svg></span><span>After the pack is printed</span></span></th><td data-label="Traditional 1D barcode">Nothing to update</td><td data-label="GS1-enabled 2D barcode">The page a link points to can be updated without reprinting</td></tr>
                  <tr><th scope="row"><span className="ts-sr-rowh"><span className="ts-sr-rowicon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="7" y="2.5" width="10" height="19" rx="2.5" /><path d="M11 18.5h2" /></svg></span><span>Scanned on customer’s phone</span></span></th><td data-label="Traditional 1D barcode">Not designed for phones</td><td data-label="GS1-enabled 2D barcode">A QR Code with GS1 Digital Link opens a web page from the phone camera</td></tr>
                  <tr><th scope="row"><span className="ts-sr-rowh"><span className="ts-sr-rowicon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="5" y="3" width="14" height="6" rx="1.5" /><path d="M8 9v3M16 9v3" /><rect x="3" y="12" width="18" height="9" rx="2" /><path d="M7 15.5h2M11 15.5h2M15 15.5h2M7 18.5h10" /></svg></span><span>Scanned at checkout</span></span></th><td data-label="Traditional 1D barcode">Read by laser or camera scanners</td><td data-label="GS1-enabled 2D barcode">Read by imaging (camera-based) scanners and software that can process it</td></tr>
                  <tr><th scope="row"><span className="ts-sr-rowh"><span className="ts-sr-rowicon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="6.5" /><path d="M16 16l5 5" /></svg></span><span>Status in Sunrise 2027</span></span></th><td data-label="Traditional 1D barcode">Keeps working; not being removed</td><td data-label="GS1-enabled 2D barcode">The format checkouts are being prepared to read</td></tr>
                </tbody>
              </table>
            </div>
          </div>
        </section>

        {/* WHAT IS A GS1 2D BARCODE */}
        <section className="ts-sr-sec is-tint" id="gs1-2d-barcodes">
          <div className="ts-sr-wrap">
            <SrHeading eyebrow="04 · The technology">What is a GS1-enabled 2D barcode?</SrHeading>
            <p>Not every 2D barcode qualifies. A QR code that simply holds a website address is not a GS1-enabled 2D barcode. To work at the checkout, the code has to follow GS1 standards so a scanner can find the product number inside it. GS1 recognizes three types:</p>
            <div className="ts-sr-type-cards">
              <div className="ts-sr-type-card is-yes">
                <div className="ts-sr-card-icon ts-sr-ic-type is-yes" aria-hidden="true"><span className="ts-sr-codeicon is-qr" /></div>
                <h3>QR Code with GS1 Digital Link</h3>
                <p>The type that can serve both the checkout and shoppers. It looks like any other QR code, and the default camera on virtually all modern smartphones can scan it and open a web page.</p>
                <span className="ts-sr-badge-row"><span className="ts-sr-badge is-yes"><svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9" /><path d="M8 12.5l2.7 2.7L16 9.5" /></svg><span>Customer interaction on mobile</span></span></span>
              </div>
              <div className="ts-sr-type-card is-partial">
                <div className="ts-sr-card-icon ts-sr-ic-type is-partial" aria-hidden="true"><span className="ts-sr-codeicon is-dm" /></div>
                <h3>Data Matrix with GS1 Digital Link</h3>
                <p>A DataMatrix that carries the web-link format. It can connect people to the web, but not all phone cameras can read this type automatically yet, so it is not the usual choice for customers.</p>
                <span className="ts-sr-badge-row"><span className="ts-sr-badge is-partial"><svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9" /><path d="M9.6 9.6a2.5 2.5 0 1 1 3.5 2.3c-.7.4-1.1.9-1.1 1.7" /><circle cx="12" cy="16.9" r="0.6" fill="currentColor" /></svg><span>Limited phone support currently</span></span></span>
              </div>
              <div className="ts-sr-type-card is-no">
                <div className="ts-sr-card-icon ts-sr-ic-type is-no" aria-hidden="true"><span className="ts-sr-codeicon is-dm" /></div>
                <h3>GS1 DataMatrix</h3>
                <p>A compact square code that holds product data but no web link. It is widely used across the supply chain and in healthcare, where the data is meant for systems rather than shoppers.</p>
                <span className="ts-sr-badge-row"><span className="ts-sr-badge is-no"><svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9" /><path d="M9 9l6 6M15 9l-6 6" /></svg><span>No customer interaction on mobile</span></span></span>
              </div>
            </div>

            <h3 className="ts-sr-h3">How GS1 Digital Link works</h3>
            <p>GS1 Digital Link is the GS1 standard for building a web address around a product’s identity. The result is a normal web address, so a phone can open it like any other link, with the product’s number and, if wanted, extra details written into it. Here is an example of what is inside such a QR code:</p>
            <SrUrlDiagram parts={SR_LINK_PARTS} label="The parts of a GS1 Digital Link web address" />
            <ul className="ts-sr-list ts-sr-dots">
              <li style={{ "--dot": SR_LINK_PARTS[0].color }}><strong>Web address (the base URL).</strong> Where a phone is sent. It can be the brand’s own site or a service like TapScan.</li>
              <li style={{ "--dot": SR_LINK_PARTS[1].color }}><strong>Product number (GTIN).</strong> Identifies the product. It is the only part a checkout needs for the first step of Sunrise 2027.</li>
              <li style={{ "--dot": SR_LINK_PARTS[2].color }}><strong>Batch or lot (optional).</strong> Identifies the production batch an item came from, which is what makes targeted recalls possible.</li>
              <li style={{ "--dot": SR_LINK_PARTS[3].color }}><strong>Serial number (optional).</strong> Identifies one individual item.</li>
              <li style={{ "--dot": SR_LINK_PARTS[4].color }}><strong>Expiry date (optional).</strong> Written as year, month, day (YYMMDD), so 261231 is 31 December 2026.</li>
            </ul>
            <p>The short numbers in the link (01, 10, 21 and 17) are GS1 codes called Application Identifiers. Each one is a label that tells a scanner what kind of information comes next: 01 means a product number follows, 10 a batch or lot, 21 a serial number and 17 an expiry date. GS1 has more than a hundred of them, covering other kinds of data.</p>
            <p>The extras follow the GTIN in a set order: batch or lot, then serial number (an optional product variant code, 22, comes before both when it is used). A checkout scanner ignores the web address and reads the product number. A shopper’s phone follows the link. The same printed code therefore serves two audiences.</p>

            <h3 className="ts-sr-h3">What is inside the product number (GTIN)?</h3>
            <p>The GTIN is the same number that sits under a traditional UPC or EAN barcode. It is made of a few parts:</p>
            <SrUrlDiagram parts={SR_GTIN_PARTS} big label="The parts of a 14-digit GTIN" />
            <ul className="ts-sr-list ts-sr-dots">
              <li style={{ "--dot": SR_GTIN_PARTS[1].color }}><strong>GS1 Company Prefix.</strong> Issued by GS1 to the brand owner, so it identifies the company. Its length varies from company to company.</li>
              <li style={{ "--dot": SR_GTIN_PARTS[2].color }}><strong>Item reference.</strong> Chosen by the brand to identify each individual product.</li>
              <li style={{ "--dot": SR_GTIN_PARTS[3].color }}><strong>Check digit.</strong> Calculated from the other digits, so a typing or scanning mistake can be caught.</li>
              <li style={{ "--dot": SR_GTIN_PARTS[0].color }}><strong>Leading zero.</strong> Padding that makes every GTIN 14 digits long in the link. A 13-digit number gets one zero and a 12-digit UPC gets two.</li>
            </ul>

            <hr className="ts-sr-rule" />
            <div className="ts-sr-note has-mark">
              <div className="ts-sr-note-mark"><svg viewBox="122 122 234 234" width="52" height="52" fill="currentColor" role="img" aria-label="TapScan symbol" focusable="false"><path d="M314.162 123H173.404C145.589 123 123 145.86 123 174.009V313.677C123 336.268 141.071 354.556 163.394 354.556H304.152C331.967 354.556 354.556 331.696 354.556 303.547V163.879C354.556 141.288 336.485 123 314.162 123ZM344.457 305.878C344.457 322.104 331.524 335.192 315.491 335.192H171.366C155.864 335.192 143.374 322.552 143.374 306.864V163.52C143.374 147.294 156.307 134.206 172.341 134.206H315.845C331.701 134.206 344.457 147.204 344.457 163.161V305.967V305.878Z" /><path d="M224.427 166.664H184.695C178.115 166.664 172.769 172.01 172.769 178.589V218.321C172.769 224.901 178.115 230.247 184.695 230.247H224.427C231.007 230.247 236.353 224.901 236.353 218.321V178.589C236.353 172.01 231.007 166.664 224.427 166.664ZM220.452 210.371C220.452 212.565 218.67 214.347 216.476 214.347H192.645C190.452 214.347 188.67 212.565 188.67 210.371V186.54C188.67 184.347 190.452 182.565 192.645 182.565H216.476C218.67 182.565 220.452 184.347 220.452 186.54V210.371Z" /><path d="M224.427 241.081H184.695C178.115 241.081 172.769 246.427 172.769 253.006V292.739C172.769 299.319 178.115 304.665 184.695 304.665H224.427C231.007 304.665 236.353 299.319 236.353 292.739V253.006C236.353 246.427 231.007 241.081 224.427 241.081ZM220.452 284.789C220.452 286.982 218.67 288.764 216.476 288.764H192.645C190.452 288.764 188.67 286.982 188.67 284.789V260.957C188.67 258.764 190.452 256.982 192.645 256.982H216.476C218.67 256.982 220.452 258.764 220.452 260.957V284.789Z" /><path d="M301.482 166.659H261.75C255.17 166.659 249.824 172.005 249.824 178.585V218.317C249.824 224.897 255.17 230.243 261.75 230.243H301.482C308.062 230.243 313.408 224.897 313.408 218.317V178.585C313.408 172.005 308.062 166.659 301.482 166.659ZM297.507 210.367C297.507 212.56 295.725 214.342 293.532 214.342H269.7C267.507 214.342 265.725 212.56 265.725 210.367V186.535C265.725 184.342 267.507 182.56 269.7 182.56H293.532C295.725 182.56 297.507 184.342 297.507 186.535V210.367Z" /><path d="M304.985 264.979C309.098 264.979 312.427 268.308 312.427 272.42C312.427 276.533 309.098 279.862 304.985 279.862C295.723 279.862 288.242 287.362 288.223 296.625C288.223 300.736 284.894 304.065 280.782 304.065C276.67 304.065 273.341 300.736 273.341 296.625C273.341 279.137 287.499 264.979 304.985 264.979ZM304.985 240.482C309.098 240.482 312.427 243.811 312.427 247.923C312.427 252.035 309.098 255.364 304.985 255.364C293.569 255.364 283.308 259.966 275.808 267.446C268.327 274.947 263.726 285.208 263.726 296.625C263.726 300.736 260.397 304.065 256.285 304.065C252.173 304.065 248.843 300.736 248.843 296.625C248.843 265.625 273.967 240.482 304.985 240.482ZM304.692 288.615C308.961 288.615 312.427 292.081 312.427 296.35C312.427 300.619 308.961 304.085 304.692 304.085C300.423 304.085 296.957 300.619 296.957 296.35C296.957 292.081 300.423 288.615 304.692 288.615Z" fill="#FFB855" /></svg></div>
              <div className="ts-sr-note-body">
                <p className="ts-sr-note-title">How TapScan helps your brand enable the technology</p>
                <p>TapScan creates branded QR codes using GS1 Digital Link and connects them to the right product identities and destinations, so the code on the pack can also lead to a product experience you control.</p>
              </div>
            </div>
          </div>
        </section>

        {/* TIMELINE */}
        <section className="ts-sr-sec" id="timeline">
          <div className="ts-sr-wrap">
            <SrHeading eyebrow="05 · Timeline">What is the GS1 Sunrise 2027 timeline?</SrHeading>
            <div className="ts-sr-steps">
              <div className="ts-sr-step">
                <span className="ts-sr-step-tag">Now to 2027</span>
                <h3>Get ready</h3>
                <p>Retailers upgrade scanners and checkout software. Brands begin adding 2D barcodes to packaging, usually alongside the existing UPC or EAN (dual marking). Pilots and testing continue across the industry.</p>
              </div>
              <div className="ts-sr-step">
                <span className="ts-sr-step-tag">By end of 2027</span>
                <h3>Checkouts are ready</h3>
                <p>GS1’s plan aims for retail checkout systems to be able to scan and process 2D barcodes. Readiness still varies from retailer to retailer.</p>
              </div>
              <div className="ts-sr-step">
                <span className="ts-sr-step-tag">After 2027</span>
                <h3>More choice</h3>
                <p>Brands keep the traditional barcode next to the 2D barcode until about 90% of checkout scanning solutions can read GS1 2D barcodes. After that, brands can choose a 2D barcode on its own. It depends on checkout readiness, not a date on the calendar.</p>
              </div>
            </div>
            <p>These dates are an industry target, not a legal deadline. Each retailer decides its own timing, so the most useful question for any brand is when the retailers it sells to expect to be ready.</p>
          </div>
        </section>

        {/* WHO */}
        <section className="ts-sr-sec is-tint" id="who">
          <div className="ts-sr-wrap">
            <SrHeading eyebrow="06 · Who is affected">Who is affected by GS1 Sunrise 2027?</SrHeading>
            <div className="ts-sr-cards">
              <div className="ts-sr-card">
                <div className="ts-sr-card-icon ts-sr-ic-1" aria-hidden="true"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3l8 4.5v9L12 21l-8-4.5v-9z" /><path d="M12 12l8-4.5M12 12v9M12 12L4 7.5" /></svg></div>
                <h3>Brands and manufacturers</h3>
                <p>Plan to add a GS1-enabled 2D barcode to packaging. If you already print a QR code, GS1’s guidance is to update it to the GS1 Digital Link format and include the product’s GTIN.</p>
              </div>
              <div className="ts-sr-card">
                <div className="ts-sr-card-icon ts-sr-ic-2" aria-hidden="true"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l2-5h14l2 5" /><path d="M3 9h18" /><path d="M5 9v11h14V9" /><path d="M10 20v-5h4v5" /></svg></div>
                <h3>Retailers</h3>
                <p>Upgrade or replace laser scanners with imaging scanners, and update scanner and checkout software so they can read the 2D barcode and use the data inside it.</p>
              </div>
              <div className="ts-sr-card">
                <div className="ts-sr-card-icon ts-sr-ic-3" aria-hidden="true"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M7 9V3h10v6" /><rect x="3" y="9" width="18" height="8" rx="2" /><rect x="7" y="14" width="10" height="7" rx="1" /><circle cx="17.5" cy="12" r="0.6" fill="currentColor" /></svg></div>
                <h3>Packaging printers and converters</h3>
                <p>Print 2D barcodes at a quality that scans reliably, and check them as part of production. Artwork and print specifications may need to change.</p>
              </div>
              <div className="ts-sr-card">
                <div className="ts-sr-card-icon ts-sr-ic-4" aria-hidden="true"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="6" y="6" width="12" height="12" rx="2" /><rect x="9.5" y="9.5" width="5" height="5" rx="1" /><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4" /></svg></div>
                <h3>Technology providers</h3>
                <p>Checkout, inventory and product-data systems need to receive and use the new data. Brands also need a system behind the QR code that sends each scan to the right place.</p>
              </div>
            </div>
          </div>
        </section>

        {/* MYTHS */}
        <section className="ts-sr-sec" id="myths">
          <div className="ts-sr-wrap">
            <SrHeading eyebrow="07 · Common misunderstandings">What GS1 Sunrise 2027 is not</SrHeading>
            <ul className="ts-sr-ruled">
              <li><strong>It is not the end of the UPC or EAN barcode.</strong> GS1’s plan does not remove it. Until about 90% of checkout scanning solutions can read GS1 2D barcodes, a product with a 2D barcode on its pack should also carry a traditional one.</li>
              <li><strong>It is not a law.</strong> It is an industry timeline set by retailers and brands together with GS1. Retailers decide what they require from their suppliers.</li>
              <li><strong>It is not only a checkout upgrade.</strong> The checkout is the first step. The larger opportunity is the extra data and the link to shoppers that 2D barcodes make possible.</li>
              <li><strong>It does not mean any QR code will do.</strong> The code has to follow GS1 Digital Link and include the product’s GTIN.</li>
              <li><strong>It is not a finished experience by itself.</strong> What a shopper sees depends on the information, systems and content connected to the product identity. For example, a QR code alone does not create product traceability: batch- or lot-related notices need the right identifiers and connected data behind them.</li>
            </ul>
          </div>
        </section>

        {/* PREPARE */}
        <section className="ts-sr-sec is-tint" id="prepare">
          <div className="ts-sr-wrap">
            <SrHeading eyebrow="08 · Getting ready">How can a brand prepare for GS1 Sunrise 2027?</SrHeading>
            <p>Most of the early work is groundwork you can start now, before anything changes on your packaging.</p>
            <ol className="ts-sr-steps-list">
              <li><strong>Find out where your retail partners stand.</strong> Ask the retailers you sell to whether and when they plan to read 2D barcodes at the checkout, and whether they will ask suppliers to add one. Their timing sets yours.</li>
              <li><strong>Take stock of your products.</strong> List every product you sell at retail and confirm each one has a correct GTIN. These are the same product numbers already behind your UPC or EAN barcodes, and they carry over to 2D barcodes.</li>
              <li><strong>Check your GS1 membership and data.</strong> Make sure your GS1 membership is current and that the product details held against your GTINs are accurate and up to date.</li>
              <li><strong>Talk to your packaging and print partners.</strong> Ask your printers and converters whether they can print and verify 2D barcodes at the quality scanners need, and when your next artwork or packaging changes are due. A planned redesign is the easiest moment to add a code.</li>
              <li><strong>Decide who owns it and where to start.</strong> Bring brand, packaging, quality, supply chain and IT together, name one owner, and pick a small set of products to look at first.</li>
            </ol>
            <div className="ts-sr-note">
              <p className="ts-sr-note-title">What comes next</p>
              <p>Once the groundwork is done, the bigger question is what the code should do for your customers. That is where TapScan can help.</p>
            </div>
          </div>
        </section>

        {/* TAPSCAN */}
        <section className="ts-sr-sec">
          <div className="ts-sr-wrap">
            <div className="ts-sr-help">
              <h2 className="ts-sr-h2 is-plain" style={{ scrollMarginTop: 100 }}>How TapScan can help<span style={{ color: "#FFB855" }}>.</span></h2>
              <p>TapScan helps brands move from 1D barcodes to GS1-enabled 2D barcodes that also work as a connected experience.</p>
              <ul className="ts-sr-list ts-sr-help-list">
                <li><strong>Create</strong> branded QR codes using GS1 Digital Link.</li>
                <li><strong>Connect</strong> them to the right product identities and destinations.</li>
                <li><strong>Direct</strong> each scan to relevant information based on the product and scan context.</li>
                <li><strong>Measure</strong> engagement.</li>
                <li><strong>Update</strong> content over time without reprinting packaging.</li>
              </ul>
              <div className="ts-sr-help-actions">
                <a className="ts-sr-btn ts-cta-btn ts-cta-btn-primary" href="https://calendly.com/katie-aotc/20min" target="_blank" rel="noopener noreferrer" onClick={() => srTrack("How TapScan Can Help: Book a Demo")}>
                  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="16" rx="2" /><line x1="3" y1="9.5" x2="21" y2="9.5" /><line x1="8" y1="2.5" x2="8" y2="6.5" /><line x1="16" y1="2.5" x2="16" y2="6.5" /></svg>
                  <span>Book a Demo</span>
                  <span className="ts-cta-arrow" aria-hidden="true" style={{ display: "inline-flex" }}><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="12" x2="20" y2="12" /><polyline points="14 6 20 12 14 18" /></svg></span>
                </a>
                <a className="ts-sr-btn ts-cta-btn ts-cta-btn-secondary" href="index.html#dynamic-product-information" onClick={() => srTrack("How TapScan Can Help: Explore Dynamic Product Information")}>
                  <span>Explore Dynamic Product Information</span>
                  <span className="ts-cta-arrow" aria-hidden="true" style={{ display: "inline-flex" }}><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="12" x2="20" y2="12" /><polyline points="14 6 20 12 14 18" /></svg></span>
                </a>
              </div>
            </div>
          </div>
        </section>

        {/* FAQ */}
        <section className="ts-sr-sec is-tint" id="faq">
          <div className="ts-sr-wrap">
            <SrHeading eyebrow="09 · Questions answered">GS1 Sunrise 2027: frequently asked questions</SrHeading>
            <div className="ts-sr-acc">
              {SUNRISE_FAQ.map((it, i) => (
                <SrFaqItem key={i} i={i} q={it.q} a={it.a} isOpen={openFaq === i} onToggle={() => { if (openFaq !== i) srTrack("FAQ: " + it.q); setOpenFaq(openFaq === i ? -1 : i); }} />
              ))}
            </div>
          </div>
        </section>

        {/* RELATED + SOURCES */}
        <section className="ts-sr-sec is-last">
          <div className="ts-sr-wrap ts-sr-end">
            <div>
            <h2 className="ts-sr-h2 is-plain">Related reading from TapScan</h2>
            <ul className="ts-sr-related">
              <li><a href="blog/connected-packaging/" onClick={() => srTrack("Related Reading: The Barcode Is Changing. Is Your Packaging Ready?")}>The Barcode Is Changing. Is Your Packaging Ready?</a></li>
              <li><a href="blog/food-recall-packaging/" onClick={() => srTrack("Related Reading: When Food Recalls Happen, Can Your Packaging Keep Up?")}>When Food Recalls Happen, Can Your Packaging Keep Up?</a></li>
              <li><a href="blog/2027-commerce-plans/" onClick={() => srTrack("Related Reading: What 2027 Commerce Plans Are Missing at the Shelf")}>What 2027 Commerce Plans Are Missing at the Shelf</a></li>
            </ul>
            </div>
            <div>
            <h2 className="ts-sr-h2 is-plain">Sources and further reading</h2>
            <p className="ts-sr-sources" style={{ fontSize: 15 }}>This guide is based on GS1 publications. For the authoritative detail, start here:</p>
            <ul className="ts-sr-list ts-sr-sources">
              <li><SrExternal href="https://www.gs1us.org/industries-and-insights/by-topic/sunrise-2027">GS1 US: What is GS1 Sunrise 2027?</SrExternal></li>
              <li><SrExternal href="https://gs1ca.org/standards/2d-barcode/">GS1 Canada: 2D barcode standards</SrExternal></li>
              <li><SrExternal href="https://www.gs1.org/standards/gs1-digital-link">GS1: GS1 Digital Link standard</SrExternal></li>
              <li><SrExternal href="https://ref.gs1.org/guidelines/2d-in-retail/">GS1: 2D barcodes in retail implementation guideline</SrExternal></li>
            </ul>
            </div>
          </div>
        </section>
      </main>

      <window.TSFooter showContact={true} onDemo={onDemo} onContact={onContact} onLogin={onLogin} onLegal={onLegal} />
    </div>
  );
}

window.SunrisePage = SunrisePage;
