BLK-11Blocks
Company Profile & Access
A company-information block: a definition list of trade name, representative, founding date and services on the left; a pure-CSS map placeholder with address and directions on the right; and a day-by-category business-hours matrix below. The matrix borrows the row/column grammar of Japanese clinic consultation-hour tables, regrouped for corporate use with hairline rules and whitespace instead of cards. Zero image assets.
- Added:
- 2026-08-05
- Dependencies:
- None
- tags
- #company #about #access #map #table #matrix #definition-list #hairline #section #block #brand #motion #no-image #ref:keyaki-kampo-jp
Preview
BLK-11 — Company & Access
会社の輪郭と、たどり着き方。
はじめて取引する会社について知りたいことは、だいたい決まっています。 だれが、いつから、何をしているのか。そして、どこへ行けば会えるのか。
会社概要
- 商号
- First CH合同会社(First CH LLC)
- 代表者
- 代表社員 千原 隼人
- 設立
- 2024年12月
- 事業年度
- 10月1日 〜 翌9月30日
- 事業内容
- Webサイト・LP制作
- AI導入コンサルティング
- 業務システム開発
- 対応エリア
- 全国(オンライン対応)
アクセス
〒150-0001東京都渋谷区神宮前0-0-0
First CHビル 5F
- 電車
- 地下鉄◯◯線「けやき通り」駅2番出口より徒歩5分
- バス
- 市営バス「けやき通り三丁目」下車・徒歩2分
- お車
- 首都高速◯◯出口より約8分(提携駐車場3台)
営業時間
| 区分 | 月 | 火 | 水 | 木 | 金 | 土 | 日 |
|---|---|---|---|---|---|---|---|
| ご来社 | 月曜日 受付 | 火曜日 受付 | 水曜日 受付 | 木曜日 受付 | 金曜日 受付 | 土曜日 事前予約制 | 日曜日 休業 |
| オンライン | 月曜日 受付 | 火曜日 受付 | 水曜日 受付 | 木曜日 受付 | 金曜日 受付 | 土曜日 受付 | 日曜日 事前予約制 |
営業時間 10:00–19:00(土曜は 10:00–15:00)/祝日・年末年始は休業。土曜のご来社は前日までのご予約制です。
"use client";
import { useEffect, useRef, useState } from "react";
import { Montserrat } from "next/font/google";
const montserrat = Montserrat({
weight: ["600", "700"],
subsets: ["latin"],
display: "swap",
});
/**
* 会社概要・アクセス(定義リスト × 行列表 × CSS地図)
* 参考: 医療系サイト群(けやき通り診療所ほか)の「診療時間表」。曜日×区分のマスに ●/△/− を置く
* 組み方を、コーポレートの営業時間表へ転用した(見た目のコピーではなく自社ブランドで再構築)。
*
* 汎用技法メモ(web-design-playbook 還流用):
* - 行列表(matrix)は素直に <table> で組む。table-fixed +「先頭列だけ固定幅」にすると、
* 271px(375px 幅でのプレビュー実寸)でも7曜日が等分されて破綻しない。可変列に幅を書かないのが肝。
* - 記号セル(●/△/−)は視覚専用。意味は <span class="sr-only">受付</span> で渡し記号側を aria-hidden に。
* これをやらないと読み上げが「黒丸 黒丸 黒丸…」になり表の意味が消える。
* - 地図プレースホルダは画像ゼロで作れる: 温かいグレーの下地 + 白い帯(道路)+ 街区の矩形
* + 破線の線路。ピンだけ transform の scale リングで呼吸させる(opacity/transform のみ)。
* 地図は装飾なので aria-hidden にし、住所・交通は必ずテキストで併記する。
* - 脱カード: 左右カラムは lg:divide-x の hairline で仕切り、上下は border-y と余白だけで束ねる。
* 罫線を1本引くたびに箱を1つ減らせる。
*/
type ProfileRow = {
term: string;
/** 単一行のとき */
value?: string;
/** 箇条書きのとき(事業内容など) */
items?: string[];
};
const profile: ProfileRow[] = [
{ term: "商号", value: "First CH合同会社(First CH LLC)" },
{ term: "代表者", value: "代表社員 千原 隼人" },
{ term: "設立", value: "2024年12月" },
{ term: "事業年度", value: "10月1日 〜 翌9月30日" },
{
term: "事業内容",
items: [
"Webサイト・LP制作",
"AI導入コンサルティング",
"業務システム開発",
],
},
{ term: "対応エリア", value: "全国(オンライン対応)" },
];
/** 交通は「場所」と「そこからの行き方」に分け、後半を whitespace-nowrap で割らせない */
const routes: { mode: string; place: string; tail: string }[] = [
{ mode: "電車", place: "地下鉄◯◯線「けやき通り」駅", tail: "2番出口より徒歩5分" },
{ mode: "バス", place: "市営バス「けやき通り三丁目」", tail: "下車・徒歩2分" },
{ mode: "お車", place: "首都高速◯◯出口より約8分", tail: "(提携駐車場3台)" },
];
/** 診療時間表の転用: 行=区分 / 列=曜日 / マス=状態 */
const days = ["月", "火", "水", "木", "金", "土", "日"] as const;
type Mark = "open" | "appt" | "closed";
const markMeta: Record<Mark, { glyph: string; label: string; className: string }> = {
open: { glyph: "●", label: "受付", className: "text-amber-600" },
appt: { glyph: "△", label: "事前予約制", className: "text-gray-500" },
closed: { glyph: "−", label: "休業", className: "text-gray-300" },
};
const hours: { scope: string; marks: Mark[] }[] = [
{
scope: "ご来社",
marks: ["open", "open", "open", "open", "open", "appt", "closed"],
},
{
scope: "オンライン",
marks: ["open", "open", "open", "open", "open", "open", "appt"],
},
];
/** 街区の矩形(CSS地図。単位は %) */
const cityBlocks = [
{ left: 4, top: 6, w: 26, h: 22 },
{ left: 46, top: 4, w: 20, h: 16 },
{ left: 72, top: 8, w: 22, h: 26 },
{ left: 6, top: 36, w: 22, h: 18 },
{ left: 52, top: 30, w: 16, h: 14 },
{ left: 76, top: 44, w: 18, h: 20 },
{ left: 10, top: 64, w: 18, h: 16 },
{ left: 44, top: 62, w: 24, h: 14 },
];
export default function CompanyAccess() {
const rootRef = useRef<HTMLElement>(null);
const [active, setActive] = useState(false);
// 画面内に入ったらリビール開始(IO 非対応環境は即時表示)
useEffect(() => {
const el = rootRef.current;
if (!el) return;
if (typeof IntersectionObserver === "undefined") {
const id = requestAnimationFrame(() => setActive(true));
return () => cancelAnimationFrame(id);
}
const io = new IntersectionObserver(
(entries) => {
if (entries.some((e) => e.isIntersecting)) {
setActive(true);
io.disconnect();
}
},
{ threshold: 0.15 }
);
io.observe(el);
return () => io.disconnect();
}, []);
const reveal = `transition-[opacity,transform] duration-700 ease-out motion-reduce:transition-none ${
active
? "translate-y-0 opacity-100"
: "translate-y-6 opacity-0 motion-reduce:translate-y-0 motion-reduce:opacity-100"
}`;
return (
<section
ref={rootRef}
aria-labelledby="company-access-heading"
className="w-full max-w-5xl bg-white"
>
{/* ヘッダー */}
<header className="max-w-2xl">
<p
className={`${montserrat.className} text-xs font-bold tracking-[0.25em] text-amber-600 uppercase ${reveal}`}
style={{ transitionDelay: "0ms" }}
>
BLK-11 — Company & Access
</p>
<h2
id="company-access-heading"
className={`mt-4 text-[clamp(1.7rem,4vw,2.4rem)] leading-[1.3] font-bold tracking-tight text-gray-900 ${reveal}`}
style={{ transitionDelay: "80ms" }}
>
会社の輪郭と、
<span className="relative whitespace-nowrap">
たどり着き方
<span
aria-hidden="true"
className="absolute -bottom-1 left-0 h-[3px] w-full bg-amber-500/70"
/>
</span>
。
</h2>
<p
className={`mt-5 text-[15px] leading-[1.9] font-medium text-gray-600 ${reveal}`}
style={{ transitionDelay: "160ms" }}
>
はじめて取引する会社について知りたいことは、だいたい決まっています。
だれが、いつから、何をしているのか。そして、どこへ行けば会えるのか。
</p>
</header>
{/* 会社概要 / アクセス(hairline で2分割・箱で囲まない) */}
<div
className={`mt-12 grid grid-cols-1 gap-10 border-t border-gray-200 pt-10 lg:grid-cols-12 lg:gap-0 lg:divide-x lg:divide-gray-200 ${reveal}`}
style={{ transitionDelay: "240ms" }}
>
{/* 左: 会社概要(定義リスト) */}
<div className="lg:col-span-7 lg:pr-10">
<h3 className="flex items-center gap-2.5 text-sm font-bold tracking-[0.08em] text-gray-900">
<span aria-hidden="true" className="h-px w-5 flex-none bg-amber-600" />
会社概要
</h3>
<dl className="mt-5 divide-y divide-gray-100 border-t border-gray-100">
{profile.map((row) => (
<div
key={row.term}
className="grid grid-cols-[5rem_1fr] gap-x-4 py-3.5 sm:grid-cols-[7rem_1fr]"
>
<dt className="pt-[0.3em] text-[11px] font-bold tracking-[0.12em] whitespace-nowrap text-gray-500 sm:text-xs">
{row.term}
</dt>
<dd className="text-[13px] leading-[1.85] font-medium text-gray-900 sm:text-sm">
{row.items ? (
<ul className="space-y-1.5">
{row.items.map((item) => (
<li key={item} className="flex items-start gap-2.5">
<span
aria-hidden="true"
className="mt-[0.85em] h-px w-3 flex-none bg-amber-600"
/>
<span>{item}</span>
</li>
))}
</ul>
) : (
row.value
)}
</dd>
</div>
))}
</dl>
</div>
{/* 右: アクセス(CSS地図 + 住所 + 交通) */}
<div className="lg:col-span-5 lg:pl-10">
<h3 className="flex items-center gap-2.5 text-sm font-bold tracking-[0.08em] text-gray-900">
<span aria-hidden="true" className="h-px w-5 flex-none bg-amber-600" />
アクセス
</h3>
{/* 地図プレースホルダ(画像ゼロ・装飾なので aria-hidden) */}
<div
aria-hidden="true"
className="relative mt-5 aspect-[4/3] w-full overflow-hidden rounded-lg border border-gray-200 bg-[#faf9f7]"
>
{cityBlocks.map((b, i) => (
<span
key={i}
className="absolute rounded-[2px] bg-gray-200/80"
style={{
left: `${b.left}%`,
top: `${b.top}%`,
width: `${b.w}%`,
height: `${b.h}%`,
}}
/>
))}
{/* 道路(白い帯) */}
<span className="absolute top-[30%] left-0 h-[7%] w-full bg-white" />
<span className="absolute top-0 left-[34%] h-full w-[6%] bg-white" />
<span className="absolute top-[80%] -left-[10%] h-[5%] w-[130%] -rotate-[9deg] bg-white" />
{/* 線路(破線) */}
<span className="absolute top-[14%] left-0 h-px w-full bg-[repeating-linear-gradient(to_right,#c9c4bd_0_8px,transparent_8px_15px)]" />
{/* 駅マーク */}
<span className="absolute top-[14%] left-[34%] size-2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-gray-400 bg-white" />
{/* 現在地ピン(transform のみで呼吸させる) */}
<span className="absolute top-[52%] left-[58%] flex size-3 -translate-x-1/2 -translate-y-1/2 items-center justify-center">
<span className="absolute size-3 rounded-full bg-amber-500/45 animate-[ca-pulse_2.6s_ease-out_infinite] motion-reduce:animate-none" />
<span className="relative size-3 rounded-full bg-amber-600 ring-2 ring-white" />
</span>
<span
className={`${montserrat.className} absolute top-2.5 left-3 text-[9px] font-semibold tracking-[0.22em] text-gray-400 uppercase`}
>
Map
</span>
</div>
{/* 住所 */}
<p className="mt-5 text-[13px] leading-[1.9] font-medium text-gray-900 sm:text-sm">
<span className="block whitespace-nowrap text-gray-500">〒150-0001</span>
東京都渋谷区神宮前0-0-0
<br />
First CHビル 5F
</p>
{/* 交通(hairline の行) */}
<dl className="mt-5 divide-y divide-gray-100 border-t border-gray-100">
{routes.map((r) => (
<div key={r.mode} className="grid grid-cols-[3.25rem_1fr] gap-x-3 py-3">
<dt className="pt-[0.25em] text-[11px] font-bold tracking-[0.12em] whitespace-nowrap text-gray-500">
{r.mode}
</dt>
<dd className="text-[13px] leading-[1.8] text-gray-700">
{r.place}
<span className="whitespace-nowrap">{r.tail}</span>
</dd>
</div>
))}
</dl>
<div className="mt-6">
<a
href="#"
className="group inline-flex items-center gap-2 text-[13px] font-semibold text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-amber-600"
>
<span className="relative">
Googleマップで見る
<span className="absolute -bottom-1 left-0 h-px w-full origin-left scale-x-100 bg-gray-900 transition-transform duration-300 ease-out group-hover:scale-x-0 motion-reduce:transition-none" />
<span className="absolute -bottom-1 left-0 h-px w-full origin-right scale-x-0 bg-amber-600 transition-transform delay-150 duration-300 ease-out group-hover:origin-left group-hover:scale-x-100 motion-reduce:transition-none" />
</span>
<span
aria-hidden="true"
className="inline-block transition-transform duration-300 ease-out group-hover:translate-x-1 motion-reduce:transition-none"
>
→
</span>
</a>
</div>
</div>
</div>
{/* 営業時間(診療時間表の行列を転用) */}
<div
className={`mt-10 border-t border-b border-gray-200 py-10 ${reveal}`}
style={{ transitionDelay: "340ms" }}
>
<div className="flex flex-wrap items-baseline justify-between gap-x-6 gap-y-3">
<h3 className="flex items-center gap-2.5 text-sm font-bold tracking-[0.08em] text-gray-900">
<span aria-hidden="true" className="h-px w-5 flex-none bg-amber-600" />
営業時間
</h3>
<ul
aria-hidden="true"
className="flex flex-wrap items-center gap-x-4 gap-y-1 text-[11px] text-gray-500"
>
{(Object.keys(markMeta) as Mark[]).map((m) => (
<li key={m} className="flex items-center gap-1.5 whitespace-nowrap">
<span className={`text-xs ${markMeta[m].className}`}>
{markMeta[m].glyph}
</span>
{markMeta[m].label}
</li>
))}
</ul>
</div>
<table className="mt-5 w-full table-fixed border-collapse text-center">
<caption className="sr-only">
曜日ごとの営業状況(ご来社・オンライン相談)
</caption>
<thead>
<tr className="border-b border-gray-200">
<th
scope="col"
className="w-[4.5rem] pb-2.5 text-left text-[11px] font-bold tracking-[0.12em] whitespace-nowrap text-gray-500 sm:w-32 sm:text-xs"
>
区分
</th>
{days.map((d) => (
<th
key={d}
scope="col"
className={`pb-2.5 text-[11px] font-bold sm:text-xs ${
d === "日"
? "text-amber-700"
: d === "土"
? "text-gray-500"
: "text-gray-700"
}`}
>
{d}
</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{hours.map((row) => (
<tr key={row.scope}>
<th
scope="row"
className="py-3.5 text-left text-[12px] font-semibold whitespace-nowrap text-gray-900 sm:text-sm"
>
{row.scope}
</th>
{row.marks.map((m, i) => (
<td key={days[i]} className="py-3.5">
<span className="sr-only">{`${days[i]}曜日 ${markMeta[m].label}`}</span>
<span
aria-hidden="true"
className={`text-sm leading-none sm:text-base ${markMeta[m].className}`}
>
{markMeta[m].glyph}
</span>
</td>
))}
</tr>
))}
</tbody>
</table>
<p className="mt-4 text-[12px] leading-[1.9] text-gray-500">
営業時間 10:00–19:00(土曜は 10:00–15:00)/
<span className="whitespace-nowrap">祝日・年末年始は休業</span>
。土曜のご来社は前日までのご予約制です。
</p>
</div>
{/* ピンの呼吸(このブロック内で完結) */}
<style>{`
@keyframes ca-pulse {
0% { transform: scale(1); opacity: 0.55; }
70% { transform: scale(3.4); opacity: 0; }
100% { transform: scale(3.4); opacity: 0; }
}
`}</style>
</section>
);
}
Add to your project via shadcn CLI
npx shadcn@latest add https://designs.first-ch.com/r/company-access.json