First CH Designs
BLK-79Blocks

出典つき実績数値(集計期間・母数・算出式を併記)

実績数値を「裏取りできる形」で出す指標ブロック。数字を格子に並べるのではなく1指標=1明細行に置き、大きな数値(Montserrat・tabular-nums)の真下に出典を小さく1行、右列には集計期間・母数(n)・除外条件を畳まず常時併記する。算出方法は文章ではなく分子/分母を1pxの括線で挟んだ分数として組む。操作は集計期間の切り替えで、値だけでなく母数・注記・表示中の期間表記まで連動し、同じ条件で数えられない期間は「—」と理由を出して空欄にする。母数が30を下回る値は表示側の判定で自動的に「参考値」と付く。出典の種別(自社集計/第三者計測/外部アンケート)は角丸なしのラベルで示し、カウントアップや出現アニメーションは使わない。狭幅では3カラムを縦に積み替える。画像素材ゼロ・依存ゼロ・掲載数値はすべて表示サンプル。

追加日:
2026-09-26
依存:
なし
tags
#block #metrics #stats #eeat #provenance #sample-size #tabular-nums #hairline #container-query #no-image

プレビュー

実績の数字は、
数え方まで添えてお出しします

大きく出した数字の下に、集計期間・母数・算出式・出典を必ず並べています。期間を切り替えると値だけでなく母数と注記も入れ替わり、同じ条件で数えられない期間は数字を出しません。根拠の元データは、ご依頼いただければ集計手順ごとお見せできます。

いま見ている集計期間
2025-04–2026-03
直近12か月/月次の締め後に確定した数字のみ。最終更新 2026-04-15

公開している指標 5

  • 公開後3か月の問い合わせ件数の変化

    公開前の月平均と比べた増減率

    公開後3か月の月平均件数 − 公開前3か月の月平均件数公開前3か月の月平均件数

    +41.6%

    出典: 自社集計(解析と問い合わせ台帳の突合)

    自社集計参考値(母数30未満)

    集計期間
    2025-04 – 2026-03
    母数
    n = 18案件
    除外
    公開前の計測が無い案件・移転を伴う案件は母数から外す
  • 保守契約の13か月継続率

    1年の更新月を越えて続いた割合

    13か月目も契約が続いている社数12か月以上前に契約を開始した社数

    92.3%

    出典: 自社集計(契約台帳・解約日ベース)

    自社集計

    集計期間
    2025-04 – 2026-03
    母数
    n = 39社
    除外
    先方の廃業・事業譲渡による終了は解約に数えない
  • 発注から公開までの日数(中央値)

    平均ではなく中央値

    公開日 − 発注日(暦日)を小さい順に並べた中央の値(中央値なので母数で割らない)

    74日

    出典: 自社集計(発注書の日付と公開記録)

    自社集計参考値(母数30未満)

    集計期間
    2025-04 – 2026-03
    母数
    n = 18案件
    除外
    先方都合で1か月以上停止した期間は日数から差し引く
  • 主要ページの表示完了が2.5秒未満の割合

    実利用者の計測値(LCP)

    LCPが2.5秒未満だったページ数計測対象にしたページ数

    96.4%

    出典: 第三者の計測サービス(実利用者データ)

    第三者計測

    集計期間
    2025-04 – 2026-03
    母数
    n = 212ページ
    除外
    公開から30日未満のページ・計測数が100未満のページは対象外
  • 納品後アンケートの総合満足度

    5点満点の平均

    回答の合計点有効回答数

    4.6点

    出典: 外部フォームの無記名アンケート

    外部アンケート参考値(母数30未満)

    集計期間
    2025-04 – 2026-03
    母数
    n = 16件
    除外
    未回答・重複回答は有効回答に数えない
    注記
    回収率 88.9%

この表で守っていること

  • 端数は小数第1位で四捨五入し、四捨五入前の値も保管しています
  • 母数が30を下回る数字には「参考値」と付け、単独で実績として使いません
  • 同じ条件で数えられない期間は、良い数字が出ていても空欄(—)にします
  • 指標の定義を変えたときは、過去の値も新しい定義で数え直してから差し替えます

更新のタイミング

四半期の締め後に見直し、次回は 2026-07 中旬の更新予定です。

算出手順と元データの開示を依頼する

掲載している指標・数値・出典はすべて表示サンプルです。

"use client";

import { useId, useState } from "react";
import { Montserrat } from "next/font/google";

const montserrat = Montserrat({
  weight: ["500", "600", "700"],
  subsets: ["latin"],
  display: "swap",
});

/**
 * 出典つき実績数値(集計期間・母数・算出方法を数字に添える)
 * 既存の数値ブロックは「1つのKPIを特大で言い切る宣言型」と「等価な数字を格子に並べる型」で、
 * どちらも数字の出どころを持たずカウントアップで見せる型だった。本ブロックは数字を1件ずつの
 * 明細行に置き、算出式(分子/分母の分数組み)・集計期間・母数・出典の種別を畳まず常時併記する。
 * 操作は集計期間の切り替えで、値だけでなく母数と期間と注記まで連動し、期間によっては
 * 「未計測」が出る(裏取りできる形=期間と母数に紐づくことを操作で示すのが主張)。
 * 飽和idiomは英字キッカーと末尾の「→」リンクを外し、角丸とカウントアップ/出現アニメも使わない。
 * 掲載している指標・数値・出典はすべて架空の表示サンプル。画像素材ゼロ・依存ゼロ。
 *
 * 汎用技法メモ(web-design-playbook 還流用):
 * - **算出式は分数として組む**: 「A ÷ B」の一行書きは分母が読み飛ばされる。
 *   `inline-flex flex-col` の上段=分子・下段=分母にし、境目を `border-t` の1pxにすると
 *   罫線がそのまま括線になる(画像もMathMLも要らず、折り返しても意味が壊れない)。
 * - **切替時の値は key で差し替える**: 期間タブで数字を入れ替えるとき、同じ要素の textContent を
 *   書き換えると CSS animation が再生されない。`key={period}` を数字の span に付けて
 *   React にマウントし直させると、`@keyframes` の入場が毎回走る(transform/opacity のみ)。
 * - **母数と信頼度を状態から導く**: 「参考値」の但し書きを手で書くとデータ更新時に嘘になる。
 *   n < 30 を閾値にして表示側で判定すれば、値を差し替えるだけで注記が追従する。
 * - **欠測を空白にしない**: 値が無い期間は行を消さず「—」+理由を置く。行が消えると
 *   レイアウトが跳ねるうえ、都合の良い期間だけ見せている表と区別できなくなる。
 * - 数字は tabular-nums 必須。期間を切り替えても桁幅が動かないので単位の位置が揺れない。
 */

type SourceKind = "internal" | "measured" | "survey";

type Reading = {
  /** 表示値(文字列で持つ。端数処理は台帳側で済ませた形を出す) */
  value: string | null;
  /** 母数 */
  n: number;
  /** 母数の単位(社・件・ページ など) */
  nUnit: string;
  /** その期間だけの補足。欠測のときは理由を書く */
  note?: string;
};

type Metric = {
  key: string;
  name: string;
  /** 数字の意味を1行で。単位の読み方もここに含める */
  reads: string;
  unit: string;
  /** 正の変化に符号を付ける指標だけ true */
  signed?: boolean;
  /** 算出式(分子・分母) */
  numerator: string;
  denominator: string;
  kind: SourceKind;
  /** 数字の真下に置く出典1行 */
  source: string;
  /** 母数から外しているもの */
  excluded: string;
  readings: Record<PeriodKey, Reading>;
};

type PeriodKey = "recent" | "three" | "all";

const periods: { key: PeriodKey; label: string; from: string; to: string }[] = [
  { key: "recent", label: "直近12か月", from: "2025-04", to: "2026-03" },
  { key: "three", label: "過去3年", from: "2023-04", to: "2026-03" },
  { key: "all", label: "創業以来", from: "2016-05", to: "2026-03" },
];

const kindLabel: Record<SourceKind, string> = {
  internal: "自社集計",
  measured: "第三者計測",
  survey: "外部アンケート",
};

/** 母数がこれを下回る数字は参考値として扱う */
const SMALL_SAMPLE = 30;

const metrics: Metric[] = [
  {
    key: "inquiry",
    name: "公開後3か月の問い合わせ件数の変化",
    reads: "公開前の月平均と比べた増減率",
    unit: "%",
    signed: true,
    numerator: "公開後3か月の月平均件数 − 公開前3か月の月平均件数",
    denominator: "公開前3か月の月平均件数",
    kind: "internal",
    source: "自社集計(解析と問い合わせ台帳の突合)",
    excluded: "公開前の計測が無い案件・移転を伴う案件は母数から外す",
    readings: {
      recent: { value: "41.6", n: 18, nUnit: "案件" },
      three: { value: "36.2", n: 54, nUnit: "案件" },
      all: {
        value: "33.8",
        n: 142,
        nUnit: "案件",
        note: "2018年3月以前は問い合わせ数を台帳に残していないため対象外",
      },
    },
  },
  {
    key: "retention",
    name: "保守契約の13か月継続率",
    reads: "1年の更新月を越えて続いた割合",
    unit: "%",
    numerator: "13か月目も契約が続いている社数",
    denominator: "12か月以上前に契約を開始した社数",
    kind: "internal",
    source: "自社集計(契約台帳・解約日ベース)",
    excluded: "先方の廃業・事業譲渡による終了は解約に数えない",
    readings: {
      recent: { value: "92.3", n: 39, nUnit: "社" },
      three: { value: "89.5", n: 104, nUnit: "社" },
      all: { value: "86.1", n: 231, nUnit: "社" },
    },
  },
  {
    key: "lead",
    name: "発注から公開までの日数(中央値)",
    reads: "平均ではなく中央値",
    unit: "日",
    numerator: "公開日 − 発注日(暦日)を小さい順に並べた中央の値",
    denominator: "(中央値なので母数で割らない)",
    kind: "internal",
    source: "自社集計(発注書の日付と公開記録)",
    excluded: "先方都合で1か月以上停止した期間は日数から差し引く",
    readings: {
      recent: { value: "74", n: 18, nUnit: "案件" },
      three: { value: "81", n: 54, nUnit: "案件" },
      all: { value: "88", n: 142, nUnit: "案件" },
    },
  },
  {
    key: "lcp",
    name: "主要ページの表示完了が2.5秒未満の割合",
    reads: "実利用者の計測値(LCP)",
    unit: "%",
    numerator: "LCPが2.5秒未満だったページ数",
    denominator: "計測対象にしたページ数",
    kind: "measured",
    source: "第三者の計測サービス(実利用者データ)",
    excluded: "公開から30日未満のページ・計測数が100未満のページは対象外",
    readings: {
      recent: { value: "96.4", n: 212, nUnit: "ページ" },
      three: { value: "93.1", n: 524, nUnit: "ページ" },
      all: {
        value: null,
        n: 0,
        nUnit: "ページ",
        note: "実利用者計測を導入したのは2021年8月。それ以前は同じ条件で数えられないため出しません",
      },
    },
  },
  {
    key: "satisfaction",
    name: "納品後アンケートの総合満足度",
    reads: "5点満点の平均",
    unit: "点",
    numerator: "回答の合計点",
    denominator: "有効回答数",
    kind: "survey",
    source: "外部フォームの無記名アンケート",
    excluded: "未回答・重複回答は有効回答に数えない",
    readings: {
      recent: { value: "4.6", n: 16, nUnit: "件", note: "回収率 88.9%" },
      three: { value: "4.5", n: 43, nUnit: "件", note: "回収率 79.6%" },
      all: { value: "4.4", n: 96, nUnit: "件", note: "回収率 67.6%" },
    },
  },
];

const UPDATED_AT = "2026-04-15";

export default function SourcedMetricsBand() {
  const uid = useId().replace(/[^a-zA-Z0-9]/g, "");
  const [period, setPeriod] = useState<PeriodKey>("recent");
  const current = periods.find((p) => p.key === period) ?? periods[0];

  return (
    <section
      aria-labelledby={`${uid}-title`}
      className="@container w-full max-w-5xl bg-white px-5 py-10 text-gray-900 sm:px-8 sm:py-14"
    >
      <style>{`
        .sm-value{animation:sm-value-in 320ms cubic-bezier(.22,.61,.36,1) both}
        @keyframes sm-value-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
        @media (prefers-reduced-motion: reduce){
          .sm-value{animation:none}
        }
      `}</style>

      <header className="@2xl:grid @2xl:grid-cols-[minmax(0,1fr)_16rem] @2xl:gap-12">
        <div>
          <h2
            id={`${uid}-title`}
            className="text-[clamp(1.35rem,3.4vw,1.9rem)] leading-[1.5] font-bold tracking-tight"
          >
            実績の数字は、
            <br />
            数え方まで添えてお出しします
          </h2>
          <p className="mt-5 max-w-lg text-[13px] leading-[2] text-gray-600">
            大きく出した数字の下に、集計期間・母数・算出式・出典を必ず並べています。期間を切り替えると値だけでなく母数と注記も入れ替わり、同じ条件で数えられない期間は数字を出しません。根拠の元データは、ご依頼いただければ集計手順ごとお見せできます。
          </p>
        </div>

        <dl className="mt-8 border-t border-gray-900 pt-4 @2xl:mt-0">
          <dt className="text-[11px] font-bold tracking-wide text-gray-500">
            いま見ている集計期間
          </dt>
          <dd
            className={`${montserrat.className} mt-2 text-[1.35rem] leading-none font-semibold tabular-nums`}
          >
            {current.from}
            <span className="mx-1.5 text-[15px] font-medium text-gray-400">–</span>
            {current.to}
          </dd>
          <dd className="mt-3 text-[11px] leading-[1.9] text-gray-600">
            {current.label}/月次の締め後に確定した数字のみ。最終更新{" "}
            <span className={`${montserrat.className} tabular-nums`}>{UPDATED_AT}</span>
          </dd>
        </dl>
      </header>

      {/* 集計期間の切替(角丸なしのセグメント)。押されている側を反転で示す */}
      <div className="mt-10 flex flex-wrap items-baseline justify-between gap-x-6 gap-y-3 border-b border-gray-900 pb-3">
        <p className="text-[11px] font-bold tracking-wide text-gray-500">
          公開している指標{" "}
          <span className={`${montserrat.className} tabular-nums text-gray-400`}>
            {metrics.length}
          </span>
        </p>
        <div className="flex items-center gap-3 text-[11px]">
          <span id={`${uid}-period`} className="hidden text-gray-500 @sm:inline">
            集計期間
          </span>
          <div
            className="flex border border-gray-300"
            role="group"
            aria-labelledby={`${uid}-period`}
          >
            {periods.map((item) => (
              <button
                key={item.key}
                type="button"
                aria-pressed={period === item.key}
                onClick={() => setPeriod(item.key)}
                className={`px-2.5 py-1.5 font-bold whitespace-nowrap transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600 motion-reduce:transition-none ${
                  period === item.key
                    ? "bg-gray-900 text-white"
                    : "text-gray-600 hover:text-gray-900"
                }`}
              >
                {item.label}
              </button>
            ))}
          </div>
        </div>
      </div>

      {/* 列見出し(広い幅のときだけ。行と同じ grid-template を共有する) */}
      <div
        aria-hidden="true"
        className="hidden pt-3 text-[10px] text-gray-400 @2xl:grid @2xl:grid-cols-[minmax(0,12.5rem)_10rem_minmax(0,1fr)] @2xl:gap-x-6"
      >
        <span>指標と算出式</span>
        <span>値と出典</span>
        <span>裏取りの条件</span>
      </div>

      <ul className="mt-3 border-t border-gray-900">
        {metrics.map((metric) => {
          const reading = metric.readings[period];
          const missing = reading.value === null;
          const small = !missing && reading.n < SMALL_SAMPLE;
          return (
            <li
              key={metric.key}
              className="grid gap-x-6 gap-y-5 border-b border-gray-200 py-6 @2xl:grid-cols-[minmax(0,12.5rem)_10rem_minmax(0,1fr)]"
            >
              {/* 指標名と算出式(分子/分母を1pxの括線で組む) */}
              <div>
                <h3 className="text-[14px] leading-[1.6] font-bold">{metric.name}</h3>
                <p className="mt-1.5 text-[11px] leading-[1.7] text-gray-500">
                  {metric.reads}
                </p>
                <div className="mt-3 flex items-center gap-2">
                  <span
                    aria-hidden="true"
                    className={`${montserrat.className} shrink-0 text-[11px] font-semibold text-amber-700`}
                  >
                    =
                  </span>
                  <span className="inline-flex min-w-0 flex-col text-[10px] leading-[1.7] text-gray-600">
                    <span className="pb-1">{metric.numerator}</span>
                    <span className="border-t border-gray-400 pt-1">
                      {metric.denominator}
                    </span>
                  </span>
                </div>
              </div>

              {/* 値+その真下に出典1行 */}
              <div className="@2xl:border-l @2xl:border-gray-200 @2xl:pl-6">
                {missing ? (
                  <p
                    className={`${montserrat.className} text-[2rem] leading-none font-semibold text-gray-300`}
                  >
                    —
                  </p>
                ) : (
                  <p className="flex items-baseline gap-1 whitespace-nowrap">
                    <span
                      key={period}
                      className={`${montserrat.className} sm-value text-[clamp(1.85rem,5cqw,2.5rem)] leading-[0.95] font-bold tracking-tight tabular-nums`}
                    >
                      {metric.signed ? "+" : ""}
                      {reading.value}
                    </span>
                    <span className="text-[13px] font-bold text-gray-500">
                      {metric.unit}
                    </span>
                  </p>
                )}
                <p className="mt-2.5 text-[10px] leading-[1.7] text-gray-500">
                  出典: {metric.source}
                </p>
                <p className="mt-2 flex flex-wrap items-center gap-2">
                  <span
                    className={`px-1.5 py-0.5 text-[10px] font-bold ${
                      metric.kind === "internal"
                        ? "border border-gray-300 text-gray-600"
                        : "border border-amber-600 bg-amber-50 text-amber-800"
                    }`}
                  >
                    {kindLabel[metric.kind]}
                  </span>
                  {small ? (
                    <span className="border border-gray-900 px-1.5 py-0.5 text-[10px] font-bold">
                      参考値(母数30未満)
                    </span>
                  ) : null}
                </p>
              </div>

              {/* 裏取りの条件(畳まず常時出す) */}
              <dl className="grid gap-x-6 gap-y-3 text-[11px] leading-[1.8] @xl:grid-cols-2 @2xl:grid-cols-1">
                <div className="flex gap-3">
                  <dt className="w-14 shrink-0 font-bold text-gray-500">集計期間</dt>
                  <dd className={`${montserrat.className} tabular-nums text-gray-700`}>
                    {current.from} – {current.to}
                  </dd>
                </div>
                <div className="flex gap-3">
                  <dt className="w-14 shrink-0 font-bold text-gray-500">母数</dt>
                  <dd className="text-gray-700">
                    {missing ? (
                      <span className="text-gray-400">—</span>
                    ) : (
                      <>
                        <span className={`${montserrat.className} tabular-nums`}>
                          n = {reading.n}
                        </span>
                        <span className="ml-1 text-gray-500">{reading.nUnit}</span>
                      </>
                    )}
                  </dd>
                </div>
                <div className="flex gap-3 @xl:col-span-2 @2xl:col-span-1">
                  <dt className="w-14 shrink-0 font-bold text-gray-500">除外</dt>
                  <dd className="text-gray-600">{metric.excluded}</dd>
                </div>
                {reading.note ? (
                  <div className="flex gap-3 @xl:col-span-2 @2xl:col-span-1">
                    <dt className="w-14 shrink-0 font-bold text-gray-500">注記</dt>
                    <dd className="border-l-2 border-amber-600 pl-3 text-gray-700">
                      {reading.note}
                    </dd>
                  </div>
                ) : null}
              </dl>
            </li>
          );
        })}
      </ul>

      {/* 表全体にかかる約束(読み方 → 根拠の請求先) */}
      <div className="mt-8 border-t-2 border-gray-900 pt-6 @2xl:grid @2xl:grid-cols-[minmax(0,1fr)_16rem] @2xl:gap-10">
        <div>
          <p className="text-[13px] leading-[1.9] font-bold">この表で守っていること</p>
          <ul className="mt-3 max-w-xl space-y-2 text-[12px] leading-[1.9] text-gray-600">
            {[
              "端数は小数第1位で四捨五入し、四捨五入前の値も保管しています",
              "母数が30を下回る数字には「参考値」と付け、単独で実績として使いません",
              "同じ条件で数えられない期間は、良い数字が出ていても空欄(—)にします",
              "指標の定義を変えたときは、過去の値も新しい定義で数え直してから差し替えます",
            ].map((item) => (
              <li key={item} className="flex gap-2.5">
                <span
                  aria-hidden="true"
                  className="mt-[0.75em] h-px w-2.5 shrink-0 bg-amber-600"
                />
                {item}
              </li>
            ))}
          </ul>
        </div>
        <div className="mt-6 border-t border-gray-200 pt-4 @2xl:mt-0 @2xl:border-t-0 @2xl:border-l @2xl:border-gray-200 @2xl:pt-0 @2xl:pl-6">
          <p className="text-[11px] font-bold tracking-wide text-gray-500">
            更新のタイミング
          </p>
          <p className="mt-2 text-[12px] leading-[1.9] text-gray-600">
            四半期の締め後に見直し、次回は{" "}
            <span className={`${montserrat.className} tabular-nums`}>2026-07</span>{" "}
            中旬の更新予定です。
          </p>
          <p className="mt-4 text-[12px] leading-[1.9] text-gray-600">
            <a
              href="#"
              className="font-bold text-gray-900 underline decoration-amber-600 decoration-2 underline-offset-4 transition-colors duration-200 hover:text-amber-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600 motion-reduce:transition-none"
            >
              算出手順と元データの開示を依頼する
            </a>
          </p>
          <p className="mt-4 text-[10px] leading-[1.8] text-gray-400">
            掲載している指標・数値・出典はすべて表示サンプルです。
          </p>
        </div>
      </div>
    </section>
  );
}

shadcn CLI でプロジェクトに追加

npx shadcn@latest add https://designs.first-ch.com/r/sourced-metrics-band.json