> ## Documentation Index
> Fetch the complete documentation index at: https://docs.acelabusa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Introduction to Timber Curtain Walls

> This course is designed to introduce architects and designers to timber curtain wall systems and provide a base of knowledge about the system's components, thermal performance, and installation considerations.

export const Quiz = ({contentIdentifier, apiBaseUrl, appBaseUrl, authPath = "/docs-auth", authMessageType = "acelab:docs-auth", quizPath = "/lms/learning-content/quiz?pagePath={contentIdentifier}", submissionPath = "/lms/learning-content/quiz/submissions?pagePath={contentIdentifier}", authTimeoutMs = 5000}) => {
  const QUIZ_PHASES = {
    Authenticating: "authenticating",
    Loading: "loading",
    Ready: "ready",
    Submitting: "submitting",
    Passed: "passed",
    Failed: "failed",
    Anonymous: "anonymous",
    Error: "error"
  };
  const TOKEN_STORAGE_KEY = "acelab:docs-auth-token";
  const localDocsHostnames = new Set(["localhost", "127.0.0.1", "[::1]"]);
  const isLocalDocsPreview = typeof window !== "undefined" && localDocsHostnames.has(window.location.hostname);
  const resolvedApiBaseUrl = apiBaseUrl || (isLocalDocsPreview ? "http://localhost:5100/api" : "https://acelab-api-prod-178528813198.us-east4.run.app/api");
  const resolvedAppBaseUrl = appBaseUrl || (isLocalDocsPreview ? "http://localhost:3000" : "https://app.acelabusa.com");
  const inferredContentIdentifier = typeof window === "undefined" ? "" : `/${window.location.pathname.split("/").filter(Boolean).join("/")}`;
  const resolvedContentIdentifier = String(contentIdentifier || "").trim() || inferredContentIdentifier;
  const trimTrailingSlash = value => String(value || "").replace(/\/+$/, "");
  const buildUrl = (baseUrl, path) => {
    const base = trimTrailingSlash(baseUrl);
    const normalizedPath = String(path || "").startsWith("/") ? path : `/${path}`;
    return `${base}${normalizedPath}`;
  };
  const buildContentPath = (pathTemplate, identifier) => pathTemplate.replace("{contentIdentifier}", encodeURIComponent(identifier));
  const readStoredToken = () => {
    try {
      return sessionStorage.getItem(TOKEN_STORAGE_KEY);
    } catch {
      return null;
    }
  };
  const storeToken = authToken => {
    try {
      if (authToken) {
        sessionStorage.setItem(TOKEN_STORAGE_KEY, authToken);
      } else {
        sessionStorage.removeItem(TOKEN_STORAGE_KEY);
      }
    } catch {}
  };
  const requestAuthToken = ({appBaseUrl: requestedAppBaseUrl, authPath: requestedAuthPath, authMessageType: requestedMessageType, timeoutMs}) => new Promise(resolve => {
    const appOrigin = new URL(requestedAppBaseUrl).origin;
    const iframe = document.createElement("iframe");
    let settled = false;
    const finish = authToken => {
      if (settled) return;
      settled = true;
      window.removeEventListener("message", onMessage);
      window.clearTimeout(timeoutId);
      iframe.remove();
      resolve(authToken);
    };
    const onMessage = event => {
      if (event.origin !== appOrigin || event.source !== iframe.contentWindow) {
        return;
      }
      if (!event.data || event.data.type !== requestedMessageType) {
        return;
      }
      finish(typeof event.data.token === "string" ? event.data.token : null);
    };
    const timeoutId = window.setTimeout(() => finish(null), timeoutMs);
    window.addEventListener("message", onMessage);
    iframe.src = buildUrl(requestedAppBaseUrl, requestedAuthPath);
    iframe.title = "Acelab sign-in check";
    iframe.setAttribute("aria-hidden", "true");
    iframe.style.display = "none";
    document.body.appendChild(iframe);
  });
  const parseErrorMessage = async (response, fallback) => {
    try {
      const body = await response.json();
      return body?.message || body?.error || fallback;
    } catch {
      return fallback;
    }
  };
  const validateQuiz = quizToValidate => {
    if (!quizToValidate || !Array.isArray(quizToValidate.questions) || quizToValidate.questions.length === 0) {
      throw new Error("This quiz does not have any questions yet.");
    }
    quizToValidate.questions.forEach(question => {
      if (question.id === undefined || !question.text || !Array.isArray(question.options) || question.options.length < 2 || question.options.some(option => option.id === undefined || !option.text)) {
        throw new Error("The quiz service returned an invalid question.");
      }
    });
    return quizToValidate;
  };
  const validateSubmissionResult = submissionResult => {
    if (!submissionResult || typeof submissionResult.passed !== "boolean" || !Number.isInteger(submissionResult.score) || !Number.isInteger(submissionResult.correctAnswerCount) || !Number.isInteger(submissionResult.incorrectAnswerCount)) {
      throw new Error("The quiz service returned an invalid grading result.");
    }
    return submissionResult;
  };
  const [phase, setPhase] = useState(QUIZ_PHASES.Authenticating);
  const [quiz, setQuiz] = useState(null);
  const [answers, setAnswers] = useState({});
  const [aiaNumber, setAiaNumber] = useState("");
  const [token, setToken] = useState(null);
  const [result, setResult] = useState(null);
  const [errorMessage, setErrorMessage] = useState("");
  const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
  const quizContainer = useRef(null);
  const authPopup = useRef(null);
  const authPopupPending = useRef(false);
  const authTokenCheckInFlight = useRef(false);
  const authQuizReloadInFlight = useRef(false);
  const scrollIntoView = () => {
    window.setTimeout(() => {
      quizContainer.current?.scrollIntoView({
        behavior: "smooth",
        block: "center"
      });
    }, 0);
  };
  const loadQuiz = async authToken => {
    if (!resolvedApiBaseUrl) {
      throw new Error("The quiz API URL has not been configured.");
    }
    const url = buildUrl(resolvedApiBaseUrl, buildContentPath(quizPath, resolvedContentIdentifier));
    const response = await fetch(url, {
      headers: {
        Accept: "application/json",
        Authorization: `Bearer ${authToken}`
      }
    });
    if (response.status === 401) {
      storeToken(null);
      setToken(null);
      setPhase(QUIZ_PHASES.Anonymous);
      return null;
    }
    if (!response.ok) {
      throw new Error(await parseErrorMessage(response, "We could not load this quiz."));
    }
    return validateQuiz(await response.json());
  };
  useEffect(() => {
    let cancelled = false;
    const initialize = async () => {
      setErrorMessage("");
      setResult(null);
      setAnswers({});
      setAiaNumber("");
      setQuiz(null);
      setCurrentQuestionIndex(0);
      if (!resolvedContentIdentifier) {
        setErrorMessage("This page is missing its quiz content identifier.");
        setPhase(QUIZ_PHASES.Error);
        return;
      }
      try {
        let authToken = readStoredToken();
        if (!authToken) {
          setPhase(QUIZ_PHASES.Authenticating);
          authToken = await requestAuthToken({
            appBaseUrl: resolvedAppBaseUrl,
            authPath,
            authMessageType,
            timeoutMs: authTimeoutMs
          });
        }
        if (cancelled) return;
        if (!authToken) {
          setPhase(QUIZ_PHASES.Anonymous);
          return;
        }
        storeToken(authToken);
        setToken(authToken);
        setPhase(QUIZ_PHASES.Loading);
        const loadedQuiz = await loadQuiz(authToken);
        if (cancelled || !loadedQuiz) return;
        setQuiz(loadedQuiz);
        setAiaNumber(loadedQuiz.aiaNumber || "");
        setPhase(QUIZ_PHASES.Ready);
      } catch (error) {
        if (cancelled) return;
        setErrorMessage(error instanceof Error ? error.message : "We could not load this quiz.");
        setPhase(QUIZ_PHASES.Error);
      }
    };
    initialize();
    return () => {
      cancelled = true;
    };
  }, [resolvedContentIdentifier, resolvedApiBaseUrl, resolvedAppBaseUrl, authPath, authMessageType, quizPath, authTimeoutMs]);
  useEffect(() => {
    let cancelled = false;
    const appOrigin = new URL(resolvedAppBaseUrl).origin;
    const loadQuizAfterAuthentication = async authToken => {
      if (!authToken || authQuizReloadInFlight.current) return;
      authQuizReloadInFlight.current = true;
      authPopupPending.current = false;
      authPopup.current = null;
      try {
        storeToken(authToken);
        setToken(authToken);
        setErrorMessage("");
        setResult(null);
        setAnswers({});
        setAiaNumber("");
        setQuiz(null);
        setCurrentQuestionIndex(0);
        setPhase(QUIZ_PHASES.Loading);
        const loadedQuiz = await loadQuiz(authToken);
        if (cancelled || !loadedQuiz) return;
        setQuiz(loadedQuiz);
        setAiaNumber(loadedQuiz.aiaNumber || "");
        setPhase(QUIZ_PHASES.Ready);
      } catch (error) {
        if (cancelled) return;
        setErrorMessage(error instanceof Error ? error.message : "We could not load this quiz.");
        setPhase(QUIZ_PHASES.Error);
      } finally {
        authQuizReloadInFlight.current = false;
      }
    };
    const onAuthMessage = event => {
      if (event.origin !== appOrigin || event.source !== authPopup.current || !event.data || event.data.type !== authMessageType) {
        return;
      }
      const authToken = typeof event.data.token === "string" ? event.data.token : null;
      if (!authToken) {
        authPopupPending.current = false;
        authPopup.current = null;
        storeToken(null);
        setToken(null);
        setPhase(QUIZ_PHASES.Anonymous);
        return;
      }
      loadQuizAfterAuthentication(authToken);
    };
    const recheckAuthAfterPopup = async () => {
      if (!authPopupPending.current || authTokenCheckInFlight.current || authQuizReloadInFlight.current) {
        return;
      }
      authTokenCheckInFlight.current = true;
      try {
        const authToken = await requestAuthToken({
          appBaseUrl: resolvedAppBaseUrl,
          authPath,
          authMessageType,
          timeoutMs: authTimeoutMs
        });
        if (cancelled || !authPopupPending.current || authQuizReloadInFlight.current) {
          return;
        }
        if (authToken) {
          await loadQuizAfterAuthentication(authToken);
        } else {
          if (!authPopup.current || authPopup.current.closed) {
            authPopupPending.current = false;
            authPopup.current = null;
          }
          setPhase(QUIZ_PHASES.Anonymous);
        }
      } catch (error) {
        if (cancelled) return;
        setErrorMessage(error instanceof Error ? error.message : "We could not check your Acelab sign-in.");
        setPhase(QUIZ_PHASES.Error);
      } finally {
        authTokenCheckInFlight.current = false;
      }
    };
    const onWindowFocus = () => {
      recheckAuthAfterPopup();
    };
    const onVisibilityChange = () => {
      if (document.visibilityState === "visible") {
        recheckAuthAfterPopup();
      }
    };
    window.addEventListener("message", onAuthMessage);
    window.addEventListener("focus", onWindowFocus);
    document.addEventListener("visibilitychange", onVisibilityChange);
    return () => {
      cancelled = true;
      window.removeEventListener("message", onAuthMessage);
      window.removeEventListener("focus", onWindowFocus);
      document.removeEventListener("visibilitychange", onVisibilityChange);
    };
  }, [resolvedApiBaseUrl, authMessageType, authPath, authTimeoutMs, resolvedContentIdentifier, quizPath, resolvedAppBaseUrl]);
  const setAnswer = (questionId, optionId) => {
    setAnswers(current => ({
      ...current,
      [questionId]: optionId
    }));
  };
  const allQuestionsAnswered = quiz?.questions.every(question => answers[question.id] !== undefined) || false;
  const currentQuestion = quiz?.questions[currentQuestionIndex] || null;
  const currentQuestionAnswered = currentQuestion && answers[currentQuestion.id] !== undefined;
  const isLastQuestion = quiz && currentQuestionIndex === quiz.questions.length - 1;
  const showQuestion = questionIndex => {
    setCurrentQuestionIndex(questionIndex);
    scrollIntoView();
  };
  const previousQuestion = () => {
    if (currentQuestionIndex > 0) {
      showQuestion(currentQuestionIndex - 1);
    }
  };
  const nextQuestion = () => {
    if (currentQuestionAnswered && quiz && currentQuestionIndex < quiz.questions.length - 1) {
      showQuestion(currentQuestionIndex + 1);
    }
  };
  const submit = async event => {
    event.preventDefault();
    if (!quiz || !token || !allQuestionsAnswered) return;
    setErrorMessage("");
    setPhase(QUIZ_PHASES.Submitting);
    try {
      const url = buildUrl(resolvedApiBaseUrl, buildContentPath(submissionPath, resolvedContentIdentifier));
      const response = await fetch(url, {
        method: "POST",
        headers: {
          Accept: "application/json",
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          aiaNumber,
          answers: quiz.questions.map(question => ({
            questionId: question.id,
            optionId: answers[question.id]
          }))
        })
      });
      if (response.status === 401) {
        storeToken(null);
        setToken(null);
        setPhase(QUIZ_PHASES.Anonymous);
        return;
      }
      if (!response.ok) {
        throw new Error(await parseErrorMessage(response, "We could not submit your answers. Please try again."));
      }
      const submissionResult = validateSubmissionResult(await response.json());
      setResult(submissionResult);
      setPhase(submissionResult.passed ? QUIZ_PHASES.Passed : QUIZ_PHASES.Failed);
      scrollIntoView();
    } catch (error) {
      setErrorMessage(error instanceof Error ? error.message : "We could not submit your answers. Please try again.");
      setPhase(QUIZ_PHASES.Ready);
      scrollIntoView();
    }
  };
  const retry = () => {
    setResult(null);
    setErrorMessage("");
    setPhase(QUIZ_PHASES.Ready);
    scrollIntoView();
  };
  const takeAgain = () => {
    setAnswers({});
    setCurrentQuestionIndex(0);
    retry();
  };
  const popupAuthPath = `${authPath}${authPath.includes("?") ? "&" : "?"}popup=1`;
  const loginUrl = typeof window === "undefined" ? buildUrl(resolvedAppBaseUrl, "/login") : `${buildUrl(resolvedAppBaseUrl, "/login")}?redirect=${encodeURIComponent(window.location.href)}`;
  const signupUrl = typeof window === "undefined" ? buildUrl(resolvedAppBaseUrl, "/signup") : `${buildUrl(resolvedAppBaseUrl, "/signup")}?redirect=${encodeURIComponent(window.location.href)}`;
  const popupLoginUrl = `${buildUrl(resolvedAppBaseUrl, "/docs-login")}?redirect=${encodeURIComponent(popupAuthPath)}`;
  const openAuthPopup = (event, popupUrl) => {
    event.preventDefault();
    const popup = window.open(popupUrl, "acelab-docs-auth");
    if (!popup) {
      setErrorMessage("Your browser blocked the sign-in tab. Allow pop-ups for this site and try again.");
      setPhase(QUIZ_PHASES.Anonymous);
      return;
    }
    authPopup.current = popup;
    authPopupPending.current = true;
    setErrorMessage("");
    setPhase(QUIZ_PHASES.Authenticating);
    popup.focus();
  };
  const isBusy = phase === QUIZ_PHASES.Authenticating || phase === QUIZ_PHASES.Loading || phase === QUIZ_PHASES.Submitting;
  const resultQuestionCount = result ? result.correctAnswerCount + result.incorrectAnswerCount : 0;
  return <section ref={quizContainer} id={`quiz-${resolvedContentIdentifier || "unconfigured"}`} className="not-prose mt-10 rounded-xl border border-zinc-200 bg-zinc-50 px-5 py-8 text-zinc-950 dark:border-zinc-800 dark:bg-zinc-900 dark:text-white lg:px-10" aria-labelledby={`quiz-${resolvedContentIdentifier || "unconfigured"}-title`}>
      <div className="border-b border-zinc-200 pb-6 dark:border-zinc-800">
        <h2 id={`quiz-${resolvedContentIdentifier || "unconfigured"}-title`} className="m-0 text-3xl font-semibold">
          Take the Quiz
        </h2>
        <p className="mb-0 mt-3 text-zinc-600 dark:text-zinc-300">
          You can take the quiz as many times as you would like.
        </p>
      </div>

      <div className="pt-8" aria-live="polite" aria-busy={isBusy}>
        {(phase === QUIZ_PHASES.Authenticating || phase === QUIZ_PHASES.Loading) && <p className="m-0 text-zinc-600 dark:text-zinc-300">
            {phase === QUIZ_PHASES.Authenticating ? "Checking your Acelab sign-in..." : "Loading quiz..."}
          </p>}

        {phase === QUIZ_PHASES.Anonymous && <div>
            <p className="mt-0">
              Sign in or create an account to take the quiz.
            </p>
            {errorMessage && <p className="mt-3 text-red-700 dark:text-red-300" role="alert">
                {errorMessage}
              </p>}
            <div className="mt-4 flex flex-wrap gap-3">
              <a href={loginUrl} onClick={event => openAuthPopup(event, popupLoginUrl)} className="rounded-lg bg-emerald-600 px-5 py-3 font-medium text-white no-underline hover:bg-emerald-700">
                Sign in
              </a>
              <a href={signupUrl} className="rounded-lg border border-zinc-300 px-5 py-3 font-medium text-zinc-950 no-underline hover:bg-zinc-100 dark:border-zinc-700 dark:text-white dark:hover:bg-zinc-800">
                Create account
              </a>
            </div>
          </div>}

        {phase === QUIZ_PHASES.Error && <div role="alert">
            <p className="mt-0 font-semibold">Unable to load the quiz!</p>
            <p className="ml-1 mb-0 text-zinc-600 dark:text-zinc-300">
              Try refreshing the page! {errorMessage}
            </p>
          </div>}

        {phase === QUIZ_PHASES.Passed && <div className="flex flex-col items-center text-center">
            <p className="m-0 text-2xl font-semibold">Congratulations!</p>
            <p className="mb-0 mt-3 max-w-lg">
              You answered {result?.correctAnswerCount ?? 0} of{" "}
              {resultQuestionCount} questions correctly and passed with a score
              of {result?.score ?? 0}%.
            </p>
            <p className="mb-0 mt-3 max-w-lg text-zinc-600 dark:text-zinc-300">
              Your continuing education certificate will be emailed to you
              shortly.
            </p>
            <button type="button" className="mt-5 border-0 bg-transparent font-semibold text-emerald-700 underline hover:no-underline dark:text-emerald-400" onClick={takeAgain}>
              Take Quiz Again
            </button>
          </div>}

        {phase === QUIZ_PHASES.Failed && <div className="flex flex-col items-center text-center">
            <p className="m-0 text-2xl font-semibold">Oops!</p>
            <p className="mb-0 mt-3 max-w-md">
              You answered {result?.correctAnswerCount ?? 0} of{" "}
              {resultQuestionCount} questions correctly and scored{" "}
              {result?.score ?? 0}%.
            </p>
            <button type="button" className="mt-5 rounded-lg bg-emerald-600 px-8 py-3 font-semibold text-white hover:bg-emerald-700" onClick={takeAgain}>
              Try Again
            </button>
          </div>}

        {(phase === QUIZ_PHASES.Ready || phase === QUIZ_PHASES.Submitting) && quiz && currentQuestion && <form onSubmit={submit}>
              {errorMessage && <div role="alert" className="mb-6 rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-red-900 dark:border-red-900 dark:bg-red-950 dark:text-red-100">
                  {errorMessage}
                </div>}

              <div className="mb-8">
                <div className="mb-2 text-sm font-medium text-zinc-600 dark:text-zinc-300">
                  <span>
                    Question {currentQuestionIndex + 1} of{" "}
                    {quiz.questions.length}
                  </span>
                </div>
                <div role="progressbar" aria-label="Quiz progress" aria-valuemin={1} aria-valuemax={quiz.questions.length} aria-valuenow={currentQuestionIndex + 1} className="h-2 overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-700">
                  <div className="h-full rounded-full bg-emerald-500 transition-all duration-300" style={{
    width: `${(currentQuestionIndex + 1) / quiz.questions.length * 100}%`
  }} />
                </div>
              </div>

              <fieldset key={currentQuestion.id} className="m-0 border-0 p-0">
                <legend className="mb-6 flex items-start gap-3 text-lg font-medium">
                  <span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-emerald-500 font-bold text-white">
                    {currentQuestionIndex + 1}
                  </span>
                  <span>{currentQuestion.text}</span>
                </legend>

                <div className="space-y-3 pl-12">
                  {currentQuestion.options.map((option, optionIndex) => {
    const inputId = `quiz-${resolvedContentIdentifier}-${currentQuestion.id}-${option.id}`;
    const selected = answers[currentQuestion.id] === option.id;
    return <label key={option.id} htmlFor={inputId} className={`flex cursor-pointer items-start gap-3 rounded-lg border px-4 py-3 transition-colors ${selected ? "border-emerald-500 bg-emerald-50 font-medium dark:bg-emerald-950" : "border-zinc-200 bg-white hover:border-zinc-400 dark:border-zinc-700 dark:bg-zinc-950 dark:hover:border-zinc-500"}`}>
                        <input id={inputId} type="radio" name={`quiz-question-${currentQuestion.id}`} value={option.id} checked={selected} required={optionIndex === 0} disabled={phase === QUIZ_PHASES.Submitting} className="mt-1 h-4 w-4 accent-emerald-600" onChange={() => setAnswer(currentQuestion.id, option.id)} />
                        <span>{option.text}</span>
                      </label>;
  })}
                </div>
              </fieldset>

              {isLastQuestion && <div className="mt-8 border-t border-zinc-200 pt-6 dark:border-zinc-800">
                  <label htmlFor={`quiz-${resolvedContentIdentifier}-aia-number`} className="block text-sm font-semibold">
                    AIA Number (optional)
                  </label>
                  <input id={`quiz-${resolvedContentIdentifier}-aia-number`} type="text" value={aiaNumber} maxLength={100} disabled={phase === QUIZ_PHASES.Submitting} className="mt-2 w-full rounded-lg border border-zinc-300 bg-white px-4 py-3 text-zinc-950 outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/20 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-700 dark:bg-zinc-950 dark:text-white" onChange={event => setAiaNumber(event.target.value)} />
                  <p className="mb-0 mt-2 text-sm text-zinc-600 dark:text-zinc-300">
                    This number will be saved to your Acelab profile and used
                    for your continuing education certificate.
                  </p>
                </div>}

              <div className="flex items-center pt-8 justify-between gap-4 border-zinc-200 dark:border-zinc-800">
                {currentQuestionIndex > 0 ? <button type="button" disabled={phase === QUIZ_PHASES.Submitting} className="rounded-lg border border-gray-200 bg-background-light px-6 py-2 font-medium text-gray-700 hover:bg-gray-600/5 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/[0.07] dark:bg-background-dark dark:text-gray-300 dark:hover:bg-gray-200/5" onClick={previousQuestion}>
                    Back
                  </button> : <span />}

                {isLastQuestion ? <button type="submit" disabled={phase === QUIZ_PHASES.Submitting || !allQuestionsAnswered} className="rounded-lg border border-gray-200 bg-background-light px-6 py-2 font-medium text-gray-700 hover:bg-gray-600/5 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/[0.07] dark:bg-background-dark dark:text-gray-300 dark:hover:bg-gray-200/5">
                    {phase === QUIZ_PHASES.Submitting ? "Submitting..." : "Submit your answers"}
                  </button> : <button type="button" disabled={!currentQuestionAnswered} className="rounded-lg border border-gray-200 bg-background-light px-6 py-2 font-medium text-gray-700 hover:bg-gray-600/5 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/[0.07] dark:bg-background-dark dark:text-gray-300 dark:hover:bg-gray-200/5" onClick={nextQuestion}>
                    Next
                  </button>}
              </div>
            </form>}
      </div>
    </section>;
};

## Introduction

### About

This course is designed to introduce architects and designers to timber curtain wall systems and provide a base of knowledge about the system’s components, thermal performance, carbon reduction benefits, structural capabilities and customizations.

### Learning Objectives

* Explain the difference between the four primary vertical fenestration types: windows, storefront, window wall, and curtain wall.
* Identify the component parts of a timber curtain wall as well as their purpose within the system.
* Explain their manufacturing and fabrication, and gain a basic knowledge of their installation methods.
* Understand the thermal performance of timber curtain walls and comparative thermal performance to aluminum curtain walls.
* Understand the relatively low environmental impacts of timber curtain walls in comparison to aluminum curtain walls.
* Understand the aesthetic, structural, and dynamic design capabilities of timber curtain walls.
* Recognize the importance of partnering with timber curtain wall manufacturers as resources throughout the design phases of a project.

## Chapter 1:Glazing System Options (Video)

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/E9l7KdK3T4U" title="Chapter 1 Acelab TCW CEU" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />

## Chapter 2: Components, Fabrication, and Installation (Video)

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/xQ6TDYTP88s" title="Chapter 2 Acelab TCW CEU" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />

## Chapter 3: Thermal Performance of Timber Curtain Walls (Video)

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/BhfXoG9_VXs" title="Chapter 3 Acelab TCW CEU" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />

## Chapter 4: Carbon Reduction and Responsible Forestry

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/rAH5qQ5wQWU" title="Chapter 4 Acelab TCW CEU" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />

## Final Quiz

To earn AIA Continuing Education Credits (CEU), you must complete the quiz below and answer all 10 questions. Feel free to retake the quiz if needed.

<Quiz />

To keep learning and earning credits, check out more courses in [On-Demand CEU](/resources/on-demand-ceu)!
