GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

チーム紹介 デザイン9選 — 人数・自己紹介の長さ・時差まで計算して見せる

チーム紹介 デザインは、人数や自己紹介の長さ、時差までデータとして計算し直す一枚をレイアウトに重ねる考え方です。人数に関わらず列崩れしない列数を計算し、自己紹介が長ければ測って続きを読むボタンを付け、パートナーロゴは枚数に関係なく同じ速さで流れる、チーム紹介ページ用セクション9種です。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

デモ

9項目とも「見た目の演出ではなく、人数・文字数・トラック幅・時刻というデータを計算し直してレイアウト・座標・速度・表示値に反映する」という同じ前提を共有しています。実測値は次の表のとおりです。

番号 項目 累積面積 強度平均 動いたフレーム
01 チームヒーロー 11.24% 54.2 12/23
02 メンバーグリッド 29.08% 53.6 8/23
03 役職タブ 2.57% 117.4 7/23
04 プロフィール詳細 17.34% 55.9 6/23
05 統計カウントアップ 1.52% 160.4 20/23
06 パートナーバー 7.33% 96.6 23/23
07 引用カード 19.91% 69.0 6/23
08 応募CTA 37.42% 63.0 10/23
09 チームフッター 1.40% 104.1 8/23

08応募CTAが累積面積37.42%で最も広いのは、バッジの色とD-dayの数字が画面の広い面積を占めて切り替わるためです。09チームフッターが1.40%で最も狭いのは、動く領域が現地時刻の文字とドットの点滅に限られる構造上の値です。04プロフィール詳細と07引用カードが23フレームのうち6フレームしか動かないのは欠陥ではなく、次のシナリオへ切り替わるまでの待機区間が長い構造だからです。強度平均は02メンバーグリッドが53.6で最も落ち着き(カード数枚が並び直るだけ)、05統計カウントアップが160.4で最も明るい(数字全体が毎フレーム描き直される)。9項目とも下限(累積面積0.08%以上・強度15以上・動いたフレーム6以上)を満たします。

チーム紹介ページの解剖 — 9つのセクションが計算するもの

会社紹介やチーム紹介ページのデザインを探すと、写真とテキストを並べただけの静的なテンプレートがほとんどです。ここで扱う9つのセクションは、その手前に計算の層を1枚重ねます。IntersectionObserverでセクションが画面に入った瞬間だけを基準にし、ResizeObserverで幅が変わった瞬間だけを基準にする — どちらも「毎フレーム見張る」のではなく「変化の瞬間だけ拾う」設計です。

何が入力で何が出力か

入力は人数・文字の長さ・トラックの実測幅・保存されたタイムゾーン文字列・固定基準時刻の5種類です。出力はCSS変数(--delay・--cols・--ix・--iw・--dur)か、DOMに直接書き込むテキストのどちらかで、キーフレーム自体は一切変えません。

なぜdocument全体ではなくCSS変数に計算結果を込めるのか

計算した数値をJavaScriptで毎フレームstyleに書き込む代わりにCSS変数へ一度だけ込めておくと、トランジションやアニメーションはブラウザのコンポジタ側で滑らかに補間されます。人数が変わるたびに再計算が走っても、実際に動く部分はCSSの遷移に任せられるので、計算のコストと描画のコストが分離されます。

セクション別コード

チーム紹介 デザインを検索して来た人は、まずこの9項目の実装から探すと早いです。自動再生は各項目内で固定シナリオを順番に切り替えるだけで、Math.random() は一度も使いません。

01team-hero — 人数から登場の遅延を計算して順に現れる写真の列

IntersectionObserver でセクションが画面に入った瞬間だけを基準にし、各写真のインデックスに固定間隔を掛けてCSS変数--delayに入れることで順番に現れます。人数が増えても総登場時間が無限に伸びないよう、インデックスをチーム人数で割った余りで間隔を圧縮し直します。等身大の顔写真ではなく、シード固定乱数で塗った円形キャンバスにイニシャルを重ねてアバターの代わりにしています。

チームヒーロー — 人数から登場の遅延を計算して順に現れる写真の列
新しいタブで開く

01 · チームヒーロー — 人数から登場の遅延を計算して順に現れる写真の列 — 計測 累積面積 11.245% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

IntersectionObserverCSS custom propertiesprefers-reduced-motion

ENTRANCE_MSを人数で割ってstepを毎回作り直す

登場予算はENTRANCE_MS 480msの定数1つだけで、人数[5, 7, 4, 6]が切り替わるたびにstep = ENTRANCE_MS / countを計算し直します。

  var ENTRANCE_MS = 480;   // 登場予算 — 人数と無関係に固定
  var counts = [5, 7, 4, 6];
  var idx = 0;

  // 登場間隔の計算 — 予算を今回の人数で割った商がstep、インデックスを人数で割った余りで
  // 毎周期ごとに圧縮幅を合わせ直す(headcountが変わっても同じ式を再利用)
  function layout(count) {
    var step = ENTRANCE_MS / count;
    avatars.forEach(function (el, i) {
      var visible = i < count;
      el.style.display = visible ? '' : 'none';
      if (visible) el.style.setProperty('--delay', ((i % count) * step).toFixed(1) + 'ms');
    });

02team-member-grid — 最終行の人数を計算して孤立カードをなくす配置

チーム人数を列数で割った余りで最終行に何人残るかを計算し、余りが1〜2人の時だけ--cols変数で最終行の列数を減らして中央に寄せます。ResizeObserverで幅が変わり列数自体が変わる時も同じ計算をやり直します。シナリオは[7, 8, 9, 10]人で循環し、余りが3人以上の時は列数を変えずそのまま並べます。

メンバーグリッド — 最終行の人数を計算して孤立カードをなくす配置
新しいタブで開く

02 · メンバーグリッド — 最終行の人数を計算して孤立カードをなくす配置 — 計測 累積面積 29.079% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

CSS GridResizeObserverCSS custom properties

remainderとtailCountで最終行だけ別扱いにする

MIN_CARD 76pxでfloor除算した列数からremainder = count % colsを出し、1か2の時だけ最終行を別のflexコンテナに移します。

  var cols = 4;   // 初期値 — ResizeObserverが実測幅で再計算する
  var MIN_CARD = 76;

  function computeCols(width) {
    var c = Math.floor(width / MIN_CARD);
    return Math.max(2, Math.min(5, c || 4));
  }

  function layout(count) {
    var remainder = count % cols;
    var tailCount = (remainder === 1 || remainder === 2) ? remainder : 0;
    var headCount = count - tailCount;
    head.style.setProperty('--cols', String(cols));
    head.innerHTML = '';
    tail.innerHTML = '';
    for (var i = 0; i < headCount; i++) head.appendChild(card(i));
    for (var j = 0; j < tailCount; j++) tail.appendChild(card(headCount + j));
    tail.style.display = tailCount > 0 ? 'flex' : 'none';

03team-role-tabs — フィルタ結果の人数をタブラベルに計算して付ける切り替え

メンバー配列をroleでfilterして各タブラベルの人数(「デザイン (4)」)を計算し続け、選択タブのgetBoundingClientRectで左端座標と幅を読んで下線インジケーターのtranslateXとwidthをその値に合わせます。タブが増えても計算方法は変わりません。構成はデザイン4名・開発4名・運用2名の10人で、KEYS配列はall・design・dev・opsの4つです。

役職タブ — フィルタ結果の人数をタブラベルに計算して付ける切り替え
新しいタブで開く

03 · 役職タブ — フィルタ結果の人数をタブラベルに計算して付ける切り替え — 計測 累積面積 2.572% · 動いたフレーム 7/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

filtergetBoundingClientRecttransform: translateX

getBoundingClientRectの実測値だけを信じる

タブの座標をインデックス×固定幅で計算すると幅の違うラベルで下線がずれるため、必ず実測のleftとwidthを読みます。

  function moveIndicator(tab) {
    var barRect = bar.getBoundingClientRect();
    var tabRect = tab.getBoundingClientRect();
    var ix = tabRect.left - barRect.left;
    var iw = tabRect.width;
    indicator.style.setProperty('--ix', ix.toFixed(1) + 'px');
    indicator.style.setProperty('--iw', iw.toFixed(1) + 'px');
  }

  function renderPanel(key) {
    var list = key === 'all' ? members : members.filter(function (m) { return m.role === key; });
    panel.innerHTML = '';
    list.slice(0, 6).forEach(function (m) {
      var chip = document.createElement('div');
      chip.className = 'tabs__chip';
      chip.setAttribute('role', 'listitem');

04team-profile-detail — 自己紹介の高さを測って続きを読むボタンの有無を決めるカード

自己紹介要素にclampを適用した状態のscrollHeightと外した状態のscrollHeightを比較して実際に切れているかをまず計算し、差がなければ続きを読むボタン自体を描画しません。短い紹介文に無駄なボタンが残る失敗をこの計算が防ぎます。変形はclamp行数[3, 2]で、このzipには2行版が入っています。

プロフィール詳細 — 自己紹介の高さを測って続きを読むボタンの有無を決めるカード
新しいタブで開く

04 · プロフィール詳細 — 自己紹介の高さを測って続きを読むボタンの有無を決めるカード — 計測 累積面積 17.345% · 動いたフレーム 6/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scrollHeightclassList.togglearia-expanded

fullHeightとclampedHeightの差で表示を決める

差が1pxを超えて初めてoverflowがtrueになり、そうでなければボタンはdisplay: noneのまま保たれます。

  function measure(lineClamp) {
    bio.style.setProperty('--clamp', String(lineClamp));
    bio.classList.add('is-clamped');
    var clampedHeight = bio.scrollHeight;
    bio.classList.remove('is-clamped');
    var fullHeight = bio.scrollHeight;
    bio.classList.add('is-clamped');
    return fullHeight > clampedHeight + 1;
  }

  function apply(lineClamp) {
    var overflow = measure(lineClamp);
    more.style.display = overflow ? '' : 'none';
    more.setAttribute('aria-expanded', 'false');
    bio.classList.remove('is-open');
    chip.textContent = 'clamp ' + lineClamp + '行 · ' + (overflow ? 'オーバーフローO' : 'オーバーフローX');
    if (card) card.classList.toggle('is-alt');
  }

05team-stat-row — 経過時間の比率をイージング関数に通して数えるカウントアップ

requestAnimationFrameが毎フレーム渡す経過時間を総時間で割って0〜1の比率を作り、その比率をイージング関数1-(1-t)^3に通した値に目標数値を掛けて表示する整数を毎フレーム再計算します。画面に入っているかとは無関係に、スクリプトが実行されるとすぐperformance.now基準で開始します。持続時間は変形で1600ms→800msに切り替わり、このzipは800ms版です。

チーム統計行 — 経過時間の比率をイージング関数に通して数えるカウントアップ
新しいタブで開く

05 · チーム統計行 — 経過時間の比率をイージング関数に通して数えるカウントアップ — 計測 累積面積 1.517% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFrameeasing functionperformance.now

easeOutCubicで両端だけ減速させる

tをそのまま数字に掛けると等速の味気ない上昇になりますが、1 - Math.pow(1 - t, 3)を通すと立ち上がりが速く終盤だけ滑らかに減速します。

  var DEFAULT_MS = 1600;   // 既定の持続時間
  var DURATIONS = [1600, 800];
  var durIdx = 0;
  var raf = null;

  function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }

  function runOnce(ms) {
    if (raf !== null) cancelAnimationFrame(raf);
    var start = performance.now();
    var targets = nums.map(function (el) { return parseInt(el.getAttribute('data-target'), 10) || 0; });
    function step(now) {
      var t = Math.min(1, (now - start) / ms);
      var eased = easeOutCubic(t);
      nums.forEach(function (el, i) {
        el.textContent = String(Math.round(targets[i] * eased));
      });

06team-partner-bar — トラックの実測幅から速度を逆算して流れる列

ロゴを一度複製して繋いだトラックのセット1つ分をgetBoundingClientRectで測った幅を目標速度(秒あたりpx)で割るとそのトラックに合ったanimation-durationが出て、これをCSS変数に入れることでキーフレームはそのまま再生時間だけを毎回再計算します。ロゴが3個でも12個でも感じる速度は同じに保たれます。ロゴの個数はCOUNT 6個固定で、速度は変形で60px/s→30px/sに切り替わり、このzipは30px/s版です。

パートナーロゴバー — トラックの実測幅から速度を逆算して流れる列
新しいタブで開く

06 · パートナーロゴバー — トラックの実測幅から速度を逆算して流れる列 — 計測 累積面積 7.333% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

getBoundingClientRectanimation-durationCSS custom properties

setWidthをspeedで割るだけの逆算

複製した1セット分の幅をspeedで割れば、そのトラックにちょうど合う秒数が出ます。個数が変わっても式は変わりません。

  var COUNT = 6;
  for (var i = 0; i < COUNT; i++) { setA.appendChild(shapeEl(i)); }
  for (var j = 0; j < COUNT; j++) { setB.appendChild(shapeEl(j)); }
  void rnd(); // シード固定使用の印 — 順序を揺らす必要がなく消費だけする

  var SPEEDS = [60, 30];   // バリエーション06: px/s
  var speedIdx = 0;

  function apply(speed) {
    var setWidth = setA.getBoundingClientRect().width;
    if (setWidth <= 0) return;
    var durationS = setWidth / speed;
    track.style.setProperty('--dur', durationS.toFixed(2) + 's');
    chip.textContent = 'speed ' + speed + 'px/s · ' + durationS.toFixed(1) + 's/lap';
  }

07team-testimonial — 行数を逆算して引用文の大きさを決める計算

引用文のscrollHeightをgetComputedStyleで読んだline-heightで割ると実際の行数が出て、その行数が閾値を超えるとCSS変数でフォントサイズを一段小さく再描画し、常にカード内を満たすようにします。短い引用文は逆に大きいサイズのままです。目標の枠高さTARGET_Hは64px、最小・最大フォントサイズは12pxと20pxです。

チーム引用カード — 行数を逆算して引用文の大きさを決める計算
新しいタブで開く

07 · チーム引用カード — 行数を逆算して引用文の大きさを決める計算 — 計測 累積面積 19.911% · 動いたフレーム 6/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

getComputedStyleline-heightCSS custom properties

TARGET_Hを推定行数で割って上限を出す

文字数から推定した行数estLinesでTARGET_Hを割り、MIN_FONTとMAX_FONTの間にクランプするのが式の核です。

  function lineHeightRatio() {
    var cs = getComputedStyle(textEl);
    var lh = parseFloat(cs.lineHeight);
    var fs = parseFloat(cs.fontSize) || 16;
    if (!lh || isNaN(lh)) return 1.4;
    return lh / fs;
  }

  function fitFontSize(length) {
    var ratio = lineHeightRatio();
    var estLines = length > 40 ? 3 : length > 18 ? 2 : 1;
    var maxByHeight = TARGET_H / (estLines * ratio);
    var size = Math.max(MIN_FONT, Math.min(MAX_FONT, Math.floor(maxByHeight)));
    return size;
  }

08team-apply-cta — 締切までの残り日数を毎回計算して示すバッジ

締切のDateオブジェクトから現在時刻を引いたミリ秒差を1日分のミリ秒数で割り、Math.ceilで繰り上げて残り日数を整数で計算し、その値が3以下になるとバッジの色が変わりaria-liveで変化を読み上げます。締切を過ぎると同じ計算が負の値を検知して「締切」の文言に変わります。固定基準時刻EPOCHは2026-01-01T00:00:00Z、URGENT_DAYSは3日です。

応募CTA — 締切までの残り日数を毎回計算して示すバッジ
新しいタブで開く

08 · 応募CTA — 締切までの残り日数を毎回計算して示すバッジ — 計測 累積面積 37.419% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

DateMath.ceilaria-live

Date.now()ではなくEPOCHを引く

daysLeftはMath.ceil((deadline - EPOCH) / DAY_MS)の1行で、実際の現在時刻を一度も参照しないためキャプチャごとに同じ値が出ます。

  var EPOCH = new Date('2026-01-01T00:00:00Z').getTime();   // 固定基準時刻
  var DAY_MS = 86400000;
  var URGENT_DAYS = 3;

  var ROLES = [
    { name: 'フロントエンドエンジニア', deadline: '2026-01-13T00:00:00Z' },
    { name: 'プロダクトデザイナー', deadline: '2026-01-03T00:00:00Z' },
    { name: 'バックエンドエンジニア', deadline: '2026-01-21T00:00:00Z' }
  ];
  var idx = 0;

  function daysLeft(deadlineIso) {
    var deadline = new Date(deadlineIso).getTime();
    return Math.ceil((deadline - EPOCH) / DAY_MS);
  }

  function apply(i) {
    var r = ROLES[i];
    var d = daysLeft(r.deadline);
    role.textContent = r.name;
    daysEl.textContent = 'D-' + d;
    var urgent = d <= URGENT_DAYS;
    card.classList.toggle('is-urgent', urgent);
    chip.textContent = '基準日 2026-01-01 · ' + (urgent ? '急ぎ' : '余裕');
  }

09team-footer — メンバーごとの時差を計算して今の時刻を示す列

メンバーごとに保存されたIANAタイムゾーン文字列をIntl.DateTimeFormatのtimeZoneオプションに入れ、同じ瞬間をそれぞれの現地時刻として計算して示し、setIntervalで仮想の時計をSTEP_MIN 95分ずつ進めて再計算し更新します。計算された時刻が深夜帯に入るメンバーには静かな表示を重ねます。固定基準時刻は2026-01-01T09:00:00Zです。

チームフッター — メンバーごとの時差を計算して今の時刻を示す列
新しいタブで開く

09 · チームフッター — メンバーごとの時差を計算して今の時刻を示す列 — 計測 累積面積 1.403% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

Intl.DateTimeFormatsetIntervaltimeZone

hourCycle: 'h23'で深夜帯を判定する

Intl.DateTimeFormatから得た時(hour)が7未満か22以上ならis-quietクラスを付け、getTimezoneOffsetのような自前計算は使いません。

  function hourIn(zone, date) {
    var s = new Intl.DateTimeFormat('en-GB', { timeZone: zone, hour: 'numeric', hourCycle: 'h23' }).format(date);
    return parseInt(s, 10);
  }

  var elapsedMin = 0;

  function render() {
    var now = new Date(EPOCH + elapsedMin * 60000);
    MEMBERS.forEach(function (m) {
      var label = new Intl.DateTimeFormat('en-GB', { timeZone: m.zone, hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).format(now);
      m.timeEl.textContent = label;
      m.timeEl.setAttribute('datetime', now.toISOString());
      var hour = hourIn(m.zone, now);
      var quiet = hour < 7 || hour >= 22;
      m.cell.classList.toggle('is-quiet', quiet);
    });
    chip.textContent = '経過 ' + elapsedMin + '分';
  }

データが配置を変える場所 — 人数・文字数・時差

会社紹介ページ デザインの多くは、写真を差し替えるだけで完成すると考えられがちです。しかし実際には人数が5人から12人に増えるだけでグリッドの列も、登場の間隔も、最終行の余りも全部変わります。この9種が「データが配置を変える場所」と呼ぶのは、まさにこの再計算が起きる境目です。

人数が変わると何が起きるか

01はENTRANCE_MSを人数で割ったstepを作り直し、02は人数を列数で割った余りで最終行の扱いを決め、03はroleごとのfilter結果のlengthをラベルに反映します。3項目とも同じ「人数」という入力を、それぞれ違う出力(遅延・列数・ラベル数字)に変換しています。

文字数が変わると何が起きるか

04はscrollHeightの差分で文章の長さを判定し、07はlengthから推定行数を出してフォントサイズを逆算します。どちらも文字を数えるのではなく、レンダリングされた高さから逆算する点が共通しています。

時差が変わると何が起きるか

09は保存されたIANA文字列をIntl.DateTimeFormatに渡すだけで、夏時間やその地域の規則をブラウザに任せます。08の残り日数も同じように、固定した基準時刻から計算するため実行するたびに同じ値が出ます。

どこで壊れるか — 奇数人数・長い自己紹介・タイムゾーンAPI

奇数人数でグリッドが崩れるとき

02のような余り計算をせず常に同じ列数で描くと、9人を4列で並べたときに最終行へ1人だけ残る孤立カードができます。余りが1〜2人の時だけ列数を減らして中央に寄せる計算を挟むと、この孤立が消えます。

自己紹介が短いのにボタンが残るとき

04のscrollHeight比較を省いて常にボタンを描くと、1行で終わる短い自己紹介にも無駄な「続きを読む」が残ります。クランプ前後の高さを比較してから描画を決めるのがこの項目の計算です。

タイムゾーンAPIが使えないブラウザ

Intl.DateTimeFormatのtimeZoneオプションは大半のモダンブラウザで動きますが、古い環境では例外を投げることがあります。09のtimeZone呼び出しをtryで包み、失敗時はUTC表示にフォールバックする処理を足しておくと空白のまま止まりません。

登場遅延を人数で圧縮しないとき

01のインデックスをそのままi * stepに使うと、人数が増えるほど最後のメンバーの登場が際限なく遅れます。インデックスを人数で割った余りで間隔を圧縮し直す必要があります。

この9種は実際どこに付けるものか

求人ページの下に付くチーム紹介、企業サイトのメンバー欄、制作会社のポートフォリオにあるチーム一覧 — この3つの画面がこの9種を最もよく探す場所です。02のメンバーグリッドは人数が常に変わる採用ページに合い、04のプロフィール詳細は一人ずつ開いて読ませたいaboutページに合います。

拠点がバラバラなリモートチームを見せる画面では、09のフッターが今連絡してよい時間かどうかを先に教えてくれます。3つの画面に共通するのは人数が固定されていない点で、カード枚数を決め打ちしないこの構造がそのまま当てはまります。

カウントアップの速さとロゴの流れを落とすとアクセシビリティも一緒に整う

名前 変える値 手触りの違い
統計カウントアップの持続時間 05 DURATIONS 1600ms → 800ms 1600msは数字が上がる過程が目に入りやすい反面、スクロールの速いユーザーにはだらけて感じられ、800msに縮めると即効的だが桁の大きい数字は潰れて見える
パートナーロゴの流れる速度 06 SPEEDS 秒速60px → 30px 60px/sはロゴをさらっと流れる感じで軽いが1つずつ読むのは難しく、30px/sに遅めると各ロゴが目に入る代わりに全体がゆったりと見える
紹介文のクランプ行数 04 LINES 3行 → 2行 3行は紹介の文脈がある程度伝わり続きを読むのクリック率は下がるがカードが縦に伸び、2行に縮めるとカードは揃うがほぼすべてのカードにボタンが付いてわずらわしくなる

このzipは800ms・30px/s・2行の側を同梱しています。

prefers-reduced-motionでも計算は止めない

prefers-reduced-motion: reduceはmatchMediaで検出してtransitionとintervalの再生だけを止め、最終状態を即座に示します。人数フィルタの結果数・列の再配置・D-dayの計算のようなデータ計算はそのまま残ります。09のパートナーロゴバー(06)は再生を止めても、CSS変数に込めた--durの値自体はそのまま保たれます。

zipを開けるとvanillaとreactの両方が入っている

zipはucthsz2gで開きます。中にはvanillaフォルダに9項目それぞれのindex.htmlが、reactフォルダには同じ計算をコンポーネント化したtsxが入っていて、コピー&ペーストで動きます — チーム紹介 パワポで作る人も多いですが、パワポのスライドは静的な画像のままで、ここに置いたセクションはブラウザ内で人数や時差が変わるたびに実際に再計算されます。

チーム紹介でよくある質問3つ

チーム紹介ページのグリッドは何人まで対応できますか

人数の上限はコード側に決めていません。02のメンバーグリッドはcount % colsで余りを毎回計算するので、5人でも50人でも同じ式のまま列数と最終行の扱いが決まります。実際の見やすさは1行あたりの列数(MIN_CARD 76pxで決まる)に依存します。

会社紹介ページ デザインとしてこの9種をそのまま使えますか

はい、9種とも会社紹介ページの一部として置けるよう作られています。人物写真は含まれておらず、代わりにキャンバスに描いたイニシャルのアバターを使っているため、実在の人物写真がなくてもすぐに動作を確認できます。

タイムゾーン表示は本物のAPIから時刻を取得していますか

いいえ、09は固定した基準時刻(EPOCH)に仮想の経過時間を足しているだけで、外部APIへの通信は一切ありません。実際のDate.now()の代わりに固定値を使うのは、キャプチャのたびに同じ場面を再現するためです。

もっと読む

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。