#!/usr/bin/env python3
# ---------------------------------------------------------------------------
# team-replica.html — a 1:1 replica of the live "Your Team" page,
# https://www.leaguelaw.com/lawyers/ , measured at a 1440px viewport 2026-08-30.
#
# Same method as the inner service page, and the same reason: the redesign is
# derived by DIFFING against a faithful copy, so this reproduces the live page
# including its defects (the squashed 50x64 hexagon, the third printing of the
# phone number, images at thirteen different widths).
#
# THE SHELL IS NOT WRITTEN TWICE. The live team page and the live inner service
# page share a shell — 52px top bar, 43px header band, page header, 832 + 64 +
# 384 content, contact strip, Get in touch, socials, footer form + map, footer
# bottom. All of that is executed out of gen_inner_replica.py, which already
# measured it. Only what actually differs is written here:
#   * the page header  — law-1.jpg, a longer h1, breadcrumb BELOW it and static
#                        (the inner page pins its breadcrumb top-right)
#   * the entry        — the icon/intro block and the thirteen-card lawyer grid
#   * the sidebar      — the main menu, submenus collapsed, and NO form
# ---------------------------------------------------------------------------
import io, json, os, re, sys

OUT   = "/srv/apps/leaguelaw/wp-content/prototype/team-replica.html"
SHELL = "/srv/apps/leaguelaw/prototyping/gen_inner_replica.py"
HERE  = os.path.dirname(os.path.abspath(__file__))

# --- the shell, executed rather than copied --------------------------------
_shell_src = io.open(SHELL, encoding="utf-8").read()
SHELL_NS = {"__name__": "gen_inner_replica"}
exec(compile(_shell_src, SHELL, "exec"), SHELL_NS)
CSS          = SHELL_NS["CSS"]
head         = SHELL_NS["head"]
header       = SHELL_NS["header"]
top_footer   = SHELL_NS["top_footer"]
footer_bottom = SHELL_NS["footer_bottom"]
assert ".phead{" in CSS and ".cwrap{display:flex}" in CSS, "the shell is not what we expected"
assert "class=\"topbar\"" in header(), "the shell's header did not come across"

# --- the page's own data, extracted from the crawl, never retyped ----------
TEAM  = json.load(io.open(os.path.join(HERE, "team.json"), encoding="utf-8"))
MENU  = json.load(io.open(os.path.join(HERE, "team_menu.json"), encoding="utf-8"))
INTRO = io.open(os.path.join(HERE, "team_intro_body.html"), encoding="utf-8").read().strip()
assert len(TEAM) == 13, "expected thirteen cards, got %d" % len(TEAM)
assert INTRO.startswith("<h2>") and "(250) 888-0002" in INTRO
assert " " in INTRO, "the copy lost its non-breaking spaces"

UP = "/wp-content/uploads"

TEAM_CSS = r"""
/* ===========================================================================
   THE TEAM PAGE'S OWN PARTS. Everything else is the shell's, measured against
   the live inner service page and reused unchanged.
   Measured off www.leaguelaw.com/lawyers/ at 1440px, 2026-08-30.
   ======================================================================== */

/* ---------- page header: 303px, not the inner page's 252 ----------------
   The h1 is longer and the breadcrumb sits BELOW it in the flow, left
   aligned — the inner page pins its breadcrumb to the top right. Measured:
   header 303 (padding 100/100), inner block 101, h1 50.4, then a 24.9 gap,
   then the breadcrumb 25.8 with 5px of padding above its text. */
.phead.team{height:303px;background-image:url('/wp-content/uploads/law-1.jpg')}
.phead.team .container{height:101px}
.phead.team h1{line-height:50.4px}
/* the 24.9px "gap" between the h1 and the breadcrumb was not a gap: it is a
   sub-line, "Your choice for legal advice", which the box measurements could
   not see because they only measured the boxes I thought to name. It took
   putting the two pages side by side to find it. */
.phead.team .psub{font-size:15.078px;line-height:24.8787px;color:#fff}
.phead.team .crumbs{position:static;padding-top:5px;
                    font-size:12.6px;line-height:20.79px;text-align:left}

/* ---------- the intro block ---------------------------------------------
   The same shape as the inner page's .lead — a hexagon in the left margin,
   an h2, body copy — but the live page draws this hexagon 50 x 64, where the
   inner page's is 50 x 65. Both are reproduced as found: a replica that
   quietly rounds the two to the same number stops being a measuring
   instrument. (Neither is on ratio; a regular hexagon at 50 wide is 57.7.) */
/* the live entry carries the theme's `clr` clearfix class. Without it the
   trailing empty paragraph's 20px margin escapes the entry instead of being
   contained by it — the page still measures 1600 at .primary either way, but
   .entry reads 1540 instead of the live 1560. */
.entry:after{content:"";display:table;clear:both}

.icontext{padding:10px 0 40px}
.icontext .tc{position:relative;padding-left:70px}
.icontext .hex{position:absolute;left:0;top:0;width:50px;height:64px;background:var(--teal);
               clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
               display:flex;align-items:center;justify-content:center}
.icontext .hex .fa{font-size:30px;color:#fff}
.icontext h2{font-family:Raleway,"Helvetica Neue",Arial,sans-serif;font-size:26px;line-height:39px;
             font-weight:700;color:#000;margin-bottom:10px}
.icontext p{margin-bottom:20px}
.icontext .fa-phone{color:var(--body);margin:0 4px}

/* ---------- the lawyer grid ---------------------------------------------
   Four across: 4 x 183.031 + 3 x 33.2656 = 831.9 of the 832 column.
   The fourth card in every row has NO right margin — measured on the live
   page, and it is what makes four fit. With the margin left on, its margin
   box is 216.3 against 183.2 of remaining line, so it wraps and the grid
   silently becomes three across and five rows deep. Chrome places a float by
   its MARGIN box, not its border box; that 0.2px is the whole difference. */
.lgrid{overflow:hidden}
.lgrid .card{float:left;width:183.031px;margin:0 33.2656px 40px 0}
.lgrid .card:nth-child(4n){margin-right:0}
.lgrid .card .image{height:178px}
/* height is fixed and width follows the source, so the thirteen portraits
   come out at thirteen different widths — 126.3 to 143.6. Reproduced, not
   tidied: it is one of the things the redesign has to answer for. */
.lgrid .card .image img{height:170px;width:auto;display:block;margin:0 auto}
.lgrid .card .image{text-align:center}
.lgrid .card h4{font-family:"Open Sans",sans-serif;font-size:15px;line-height:22.5px;font-weight:600;
                color:#000;margin:5px 0 10px;text-transform:uppercase;text-align:center}
/* the shell paints every .entry link teal; here the theme keeps the names
   black, and the name is the whole card's link */
.lgrid .card h4 a{color:#000}
/* the shell's .entry p carries a 20px bottom margin, which the live theme
   zeroes here — leave it on and every card is 252.5 tall instead of 232.5 */
.lgrid .card p{font-size:17px;line-height:17px;color:var(--body);margin:0;text-align:center}

/* ---------- sidebar: the main menu, submenus collapsed, no form ----------
   Four rows show — What We Do, Your Team, Blogs, Careers. The other 39 are
   in the markup and hidden, which is why the widget is 204px and not 1,600.
   The live sidebar's second widget renders at zero height, so this page has
   no sidebar form at all. */
.tnav{list-style:none}
.tnav>li{position:relative}
.tnav>li>a{display:block;font-weight:700;color:#777;padding:7.532px 30px 7.532px 10.766px}
/* "Your Team" is where we are, and the live theme fills that row solid teal
   with white text — 2.60:1, which is one of the things the redesign has to
   answer for. Reproduced as found. */
.tnav>li.cur>a{background:var(--teal);color:#fff}
.tnav .chev{position:absolute;right:0;top:0;width:40px;height:38px;color:#666;font-size:14px}
.tnav li.cur .chev{color:#fff}
.tnav .chev .fa{position:relative;top:12px;left:15px}
.tnav .sub{display:none}
/* the shell sets .sform{margin-top:43px}, measured on the inner service page.
   Here the gap between the navigation widget and the form is 30px — the
   widget's own bottom margin — because this page's nav widget is the whole
   main menu rather than a claims list. Measured, not carried over. */
.sidebar .sform{margin-top:30px}
"""


def page_header():
    return """
<!-- 3 · page header (303px) -->
<header class="phead team">
  <div class="container">
    <h1>League and Williams BC Injury, Estate, Marine and Real Estate Lawyers</h1>
    <div class="psub">Your choice for legal advice</div>
    <nav class="crumbs"><a href="/"><i class="fa fa-home"></i></a><span class="sep">&raquo;</span>Our Lawyers</nav>
  </div>
</header>
"""


def entry():
    cards = "".join(
        '<div class="card"><div class="image"><a href="/lawyers/{slug}/">'
        '<img src="{up}/{img}" alt=""></a></div>'
        '<h4><a href="/lawyers/{slug}/">{name}</a></h4><p>{role}</p></div>'.format(up=UP, **c)
        for c in TEAM)
    return ("""
<!-- 4 · content + sidebar -->
<div class="container cwrap">
  <main class="primary">
    <div class="entry">

      <div class="icontext"><div class="tc">
        <span class="hex"><i class="fa fa-university"></i></span>
"""
            + INTRO + """
      </div></div>

      <div class="lgrid">""" + cards + """</div>

      <!-- the live page ends its entry with an EMPTY paragraph block, left
           behind by whoever last edited it. It renders at zero height and
           still contributes its 20px bottom margin, which is the whole of the
           difference between a 1540px entry and the live 1560. Reproduced,
           because a replica that tidies is not a measuring instrument. -->
      <p class="wp-block-paragraph"></p>

    </div>
  </main>
""")


def sidebar():
    top = [m for m in MENU if m["depth"] == 0]
    assert [m["text"] for m in top] == ["What We Do", "Your Team", "Blogs", "Careers"], \
        "the visible sidebar rows are not the four measured on the live page: %s" % [m["text"] for m in top]
    # a row gets a chevron when it has children, and "Your Team" is the page
    # we are on. Both are derived from the crawled menu, not typed in.
    idx = [i for i, m in enumerate(MENU) if m["depth"] == 0] + [len(MENU)]
    kids = {MENU[idx[k]]["text"]: any(x["depth"] > 0 for x in MENU[idx[k] + 1:idx[k + 1]])
            for k in range(len(idx) - 1)}
    assert kids == {"What We Do": True, "Your Team": True, "Blogs": False, "Careers": False}, kids
    rows = "".join(
        '<li%s><a href="%s">%s</a>%s</li>'
        % (' class="cur"' if m["href"] == "/lawyers/" else "", m["href"], m["text"],
           '<span class="chev"><i class="fa fa-chevron-right"></i></span>' if kids[m["text"]] else "")
        for m in top)
    hidden = "".join('<li><a href="%s">%s</a></li>' % (m["href"], m["text"])
                     for m in MENU if m["depth"] > 0)
    shell_sb = SHELL_NS["sidebar"]()
    i = shell_sb.index('<div class="fcard sform">')
    form = shell_sb[i:shell_sb.index("</aside>", i)].rstrip()
    assert 'placeholder="MESSAGE"' in form and "SEND MESSAGE" in form, \
        "the shell's sidebar form did not come across"

    return """
    <aside class="sidebar">
      <div class="wtitle">Navigation</div>
      <ul class="tnav">{rows}
        <ul class="sub">{hidden}</ul>
      </ul>

      {form}
    </aside>
</div>
""".format(rows=rows, hidden=hidden, form=form)


def build():
    css = CSS + TEAM_CSS
    html = head().replace(CSS, css)
    assert TEAM_CSS.strip()[:40] in html, "the team CSS was not injected"
    html = html.replace("<title>League Law &mdash; Inner Service Replica (live as-is)</title>",
                        "<title>League Law &mdash; Your Team (live replica)</title>")
    parts = [html, header(), page_header(), entry(), sidebar(),
             top_footer(), footer_bottom(), "\n</body>\n</html>\n"]
    out = "".join(parts)
    assert out.count("<h1") == 1, "expected exactly one h1"
    assert out.count('class="card"') == 13, "expected thirteen cards"
    return out


if __name__ == "__main__":
    html = build()
    dest = sys.argv[1] if len(sys.argv) > 1 else OUT
    io.open(dest, "w", encoding="utf-8").write(html)
    print("wrote %s (%d bytes)" % (dest, len(html)))
